Documentation
¶
Overview ¶
Package gh reads github.com without a token.
Every byte this package fetches is a byte a logged-out browser would get: public HTML, the JSON those pages ship inside themselves, the JSON their own front end asks for, Atom feeds, and the git smart protocol. There is no REST client here and there never will be one. The unauthenticated REST API allows sixty requests an hour, which is not enough to read one organization, and the pages are behind a CDN, which makes them faster than the API even when the API would work.
The consequence is a read-only tool. It cannot see a private repository and it cannot write anything. For that, use the official gh.
Index ¶
- Constants
- Variables
- func Classify(input string) (kind, id string, err error)
- func DefaultCacheDir() string
- func DomainDefaults(c *kit.Config)
- func Extract(rec any) (Node, []Edge, []Fact)
- func IRI(uri string) string
- func Locate(kind, id string) (string, error)
- func Page(parts ...string) string
- func RepoOf(kind, id string) (string, bool)
- func ResolveRef(want, input string) (string, error)
- func ResolveRepo(input string) (string, error)
- func SortEdges(edges []Edge)
- func SplitPathID(id string) (repo, ref, path string, ok bool)
- func SplitRepo(id string) (owner, name string, ok bool)
- func SplitThreadID(id string) (repo string, num string, ok bool)
- func Streams(format string) bool
- func TrustAtLeast(source, min string) bool
- func URI(kind, id string) string
- func WriteRDF(w io.Writer, g *Graph, o RDFOptions) error
- type Account
- type Action
- type Actor
- type Asset
- type Base
- type BlobOptions
- type Check
- type Client
- func (c *Client) Account(ctx context.Context, login string) (*Account, error)
- func (c *Client) Activity(ctx context.Context, ref string, limit int, emit func(Event) error) error
- func (c *Client) Archive(ctx context.Context, repo, ref, format string) (io.ReadCloser, error)
- func (c *Client) Blob(ctx context.Context, repo, path string, opts BlobOptions) (*File, error)
- func (c *Client) Branches(ctx context.Context, repo string, opts RefOptions, emit func(GitRef) error) error
- func (c *Client) CommitInfo(ctx context.Context, repo, sha string, opts CommitInfoOptions) (*Commit, error)
- func (c *Client) Commits(ctx context.Context, repo string, opts CommitOptions, emit func(Commit) error) error
- func (c *Client) CompareRefs(ctx context.Context, repo, base, head string, opts CompareOptions) (*Compare, error)
- func (c *Client) Contributions(ctx context.Context, login string, year int, emit func(ContributionDay) error) error
- func (c *Client) Contributors(ctx context.Context, repo string, opts ContributorOptions, ...) error
- func (c *Client) Crawl(ctx context.Context, seed string, o CrawlOptions, sink CrawlSink) error
- func (c *Client) DefaultBranch(ctx context.Context, repo string) (string, error)
- func (c *Client) Dependencies(ctx context.Context, repo string, limit int, emit func(Dependency) error) error
- func (c *Client) Dependents(ctx context.Context, repo string, limit int, emit func(Dependent) error) error
- func (c *Client) Diff(ctx context.Context, url string) (string, error)
- func (c *Client) Discussion(ctx context.Context, repo string, number int) (*Discussion, error)
- func (c *Client) Doctor(ctx context.Context, emit func(*Check) error) error
- func (c *Client) Download(ctx context.Context, repo, ref, path string, w io.Writer) (int64, error)
- func (c *Client) Estimate(ctx context.Context, seed string, o CrawlOptions) (*CrawlPlan, error)
- func (c *Client) Followers(ctx context.Context, login string, limit int, emit func(Account) error) error
- func (c *Client) Following(ctx context.Context, login string, limit int, emit func(Account) error) error
- func (c *Client) Forks(ctx context.Context, repo string, limit int, emit func(Repo) error) error
- func (c *Client) Get(ctx context.Context, rawURL string, s Surface) (*Response, error)
- func (c *Client) GetHTML(ctx context.Context, rawURL string) (*Response, error)
- func (c *Client) GetJSON(ctx context.Context, rawURL string, s Surface, v any) (*Response, error)
- func (c *Client) Gist(ctx context.Context, id string, withContent bool) (*Gist, error)
- func (c *Client) Gists(ctx context.Context, login string, limit int, emit func(Gist) error) error
- func (c *Client) GraphOf(ctx context.Context, kind, id string) (*Graph, error)
- func (c *Client) GraphOfRef(ctx context.Context, ref string) (string, string, *Graph, error)
- func (c *Client) Head(ctx context.Context, url string) (http.Header, error)
- func (c *Client) Issue(ctx context.Context, repo string, number int) (*Issue, error)
- func (c *Client) Languages(ctx context.Context, repo string, emit func(LanguageShare) error) error
- func (c *Client) Members(ctx context.Context, login string, limit int, emit func(Account) error) error
- func (c *Client) Org(ctx context.Context, login string) (*Org, error)
- func (c *Client) Page(ctx context.Context, rawURL string) (*page.Page, error)
- func (c *Client) Patch(ctx context.Context, url string) (string, error)
- func (c *Client) Poll(ctx context.Context, rawURL string, s Surface) (*Response, error)
- func (c *Client) PullCommits(ctx context.Context, repo string, number int, limit int, ...) error
- func (c *Client) PullRequest(ctx context.Context, repo string, number int) (*PullRequest, error)
- func (c *Client) Raw(ctx context.Context, repo, ref, path string) ([]byte, error)
- func (c *Client) Refs(ctx context.Context, repo string, opts RefOptions, emit func(GitRef) error) error
- func (c *Client) Release(ctx context.Context, repo, tag string, opts ReleaseOptions) (*Release, error)
- func (c *Client) Releases(ctx context.Context, repo string, opts ReleaseOptions, ...) error
- func (c *Client) Repo(ctx context.Context, id string, opts RepoOptions) (*Repo, error)
- func (c *Client) ReposAsShown(ctx context.Context, login string, limit int, emit func(Repo) error) error
- func (c *Client) SearchAccounts(ctx context.Context, query string, limit int, emit func(Account) error) error
- func (c *Client) SearchCodeBy(context.Context, string, int, func(File) error) error
- func (c *Client) SearchCommitsBy(ctx context.Context, query string, limit int, emit func(Commit) error) error
- func (c *Client) SearchDiscussionsBy(ctx context.Context, query string, limit int, emit func(Discussion) error) error
- func (c *Client) SearchIssuesAndPulls(ctx context.Context, query, typ string, limit int, emit func(Thread) error) error
- func (c *Client) SearchMarketplace(ctx context.Context, query string, limit int, emit func(Action) error) error
- func (c *Client) SearchPackagesBy(ctx context.Context, query string, limit int, emit func(Package) error) error
- func (c *Client) SearchRepositories(ctx context.Context, query string, limit int, emit func(Repo) error) error
- func (c *Client) SearchTopicsBy(ctx context.Context, query string, limit int, emit func(Topic) error) error
- func (c *Client) SearchWikisBy(ctx context.Context, query string, limit int, emit func(WikiPage) error) error
- func (c *Client) Starred(ctx context.Context, login string, limit int, emit func(Repo) error) error
- func (c *Client) Stats(ctx context.Context, repo string) (*RepoStats, error)
- func (c *Client) Stream(ctx context.Context, rawURL string) (io.ReadCloser, http.Header, error)
- func (c *Client) Tags(ctx context.Context, repo string, opts RefOptions, emit func(GitRef) error) error
- func (c *Client) Timeline(ctx context.Context, repo string, number int, limit int, ...) error
- func (c *Client) TopicPage(ctx context.Context, slug string) (*Topic, error)
- func (c *Client) Tree(ctx context.Context, repo, path string, opts TreeOptions, ...) error
- func (c *Client) Trending(ctx context.Context, opts TrendingOptions, emit func(Trending) error) error
- func (c *Client) TrendingDevelopers(ctx context.Context, opts TrendingOptions, emit func(Account) error) error
- func (c *Client) VerifyCommits(ctx context.Context, repo string, commits []*Commit) error
- type Commit
- type CommitInfoOptions
- type CommitOptions
- type Compare
- type CompareOptions
- type Config
- type ContributionDay
- type Contributor
- type ContributorOptions
- type ContributorWeek
- type CrawlOptions
- type CrawlPlan
- type CrawlSink
- type Dependency
- type Dependent
- type Discussion
- type Domain
- type Edge
- type Event
- type Fact
- type File
- type FileChange
- type Gist
- type GistFile
- type GitRef
- type Graph
- type Heading
- type Ident
- type Issue
- type Label
- type LanguageShare
- type Milestone
- type Node
- type Org
- type Package
- type PullRequest
- type RDFOptions
- type RDFWriter
- type Reaction
- type RefOptions
- type Release
- type ReleaseOptions
- type Repo
- type RepoOptions
- type RepoStats
- type Response
- type RouteInfo
- type Surface
- type Symbol
- type Thread
- type ThreadRef
- type TimelineItem
- type Topic
- type TreeEntry
- type TreeOptions
- type Trending
- type TrendingOptions
- type WikiPage
Constants ¶
const ( StatusOK = "ok" StatusWarn = "warn" StatusFail = "fail" )
The three states a check can be in. Warn exists because most of what goes wrong here is survivable: a token in the environment, a cache that cannot be written, a page that parsed but looks thinner than it should.
const ( BaseURL = "https://github.com" RawURL = "https://raw.githubusercontent.com" CodeLoad = "https://codeload.github.com" GistURL = "https://gist.github.com" GistRaw = "https://gist.githubusercontent.com" AvatarURL = "https://avatars.githubusercontent.com" OpenGraph = "https://opengraph.githubassets.com" )
The hosts. All five are public and none of them accept a credential from us.
const ( // SrcID is derived from the id structure alone. No fetch, always correct. SrcID = "id" // SrcPayload is an explicit reference in a JSON payload or a Relay result. SrcPayload = "payload" // SrcFeed is an explicit reference in an Atom feed. SrcFeed = "feed" // SrcHTML was parsed out of rendered markup with a selector. Good, and it // degrades to a missing edge rather than a wrong one when a template moves. SrcHTML = "html" // SrcText is a pattern matched in free text: #42, a bare SHA. Heuristic, and // dropped by the default --min-trust. SrcText = "text" )
The five extraction rules, in descending order of trust. Every edge carries the one that produced it, which is the field a consumer uses to decide how much to believe.
const ( // Ownership and membership. PredOwnedBy = "ownedBy" PredMemberOf = "memberOf" PredPartOf = "partOf" PredBelongsToPackage = "belongsToPackage" // Derivation. The edges that make a graph worth walking. PredForkOf = "forkOf" PredTemplateOf = "templateOf" PredMirrorOf = "mirrorOf" PredDependsOn = "dependsOn" PredUsedBy = "usedBy" // Authorship and activity. PredAuthoredBy = "authoredBy" PredCommittedBy = "committedBy" PredContributedTo = "contributedTo" PredAssignedTo = "assignedTo" PredReviewedBy = "reviewedBy" PredReviewRequestedFrom = "reviewRequestedFrom" PredMergedBy = "mergedBy" // Reference. PredReferences = "references" PredCloses = "closes" PredClosedBy = "closedBy" PredDuplicateOf = "duplicateOf" PredSubIssueOf = "subIssueOf" PredLinkedTo = "linkedTo" PredTargetsBranch = "targetsBranch" PredFromBranch = "fromBranch" PredPointsAt = "pointsAt" PredParentOf = "parentOf" // Classification. PredHasTopic = "hasTopic" PredHasLabel = "hasLabel" PredInMilestone = "inMilestone" PredWrittenIn = "writtenIn" PredLicensedUnder = "licensedUnder" PredRelatedTopic = "relatedTopic" // Social. Opt-in everywhere, because the star list of a popular repository // is thousands of pages and nobody wants that by accident. PredStarredBy = "starredBy" PredFollows = "follows" PredSponsors = "sponsors" PredReactedWith = "reactedWith" )
The predicate vocabulary. This is the complete set: an edge this tool emits has its predicate here, and adding a relation means adding a constant first.
const ( FactName = "name" FactDescription = "description" FactHomepage = "homepage" FactCreated = "created" FactUpdated = "updated" FactStars = "stars" FactForks = "forks" FactWatchers = "watchers" FactCommits = "commits" FactURI = "uri" FactAvatar = "avatar" FactState = "state" FactCount = "count" )
The literal predicates. These name Fact rows rather than edges.
const ( NSSchema = "https://schema.org/" NSGH = "https://github.com/ns#" NSGHR = "https://github.com/" NSRdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" NSRdfs = "http://www.w3.org/2000/01/rdf-schema#" NSXsd = "http://www.w3.org/2001/XMLSchema#" NSDoap = "http://usefulinc.com/ns/doap#" NSFoaf = "http://xmlns.com/foaf/0.1/" )
The namespaces.
const ( TypeInteger = "xsd:integer" TypeDecimal = "xsd:decimal" TypeBoolean = "xsd:boolean" TypeDateTime = "xsd:dateTime" )
The datatypes a Fact can carry. They are CURIEs so a Fact reads the same in every serialisation.
const ( FormatNT = "nt" FormatNQuads = "nq" FormatTurtle = "ttl" FormatJSONLD = "jsonld" )
The output formats.
const ( SearchRepos = "repositories" SearchIssues = "issues" SearchPulls = "pullrequests" SearchUsers = "users" SearchCommits = "commits" SearchDiscussions = "discussions" SearchTopics = "topics" SearchPackages = "registrypackages" SearchWikis = "wikis" SearchMarket = "marketplace" SearchCode = "code" )
The site's own names for the search types. They are not guessable (issues but pullrequests, registrypackages but wikis) which is exactly why they are constants.
const ( KindRepo = "repo" KindUser = "user" KindOrg = "org" KindIssue = "issue" KindPR = "pr" KindDiscussion = "discussion" KindCommit = "commit" KindBranch = "branch" KindTag = "tag" KindRelease = "release" KindFile = "file" KindTree = "tree" KindLabel = "label" KindMilestone = "milestone" KindTopic = "topic" KindGist = "gist" KindPackage = "package" KindAction = "action" KindWiki = "wiki" KindAdvisory = "advisory" KindCompare = "compare" // These three name records GitHub derives rather than serves. There is no // page whose address is one contributor's statistics or one day of a // calendar, so they get a URI and no canonical URL, and Locate points at // the page they were read from instead of inventing one. KindContributor = "contributor" KindContribution = "contribution" KindEvent = "event" )
The kinds. Twenty are addressable as github:// URIs; compare is a recognised route that names a range rather than a thing, and is here because people paste compare URLs.
const DefaultMinTrust = SrcHTML
DefaultMinTrust keeps everything except free-text guesses.
const Scheme = "github"
Scheme is the URI scheme this package mints and dereferences.
const UserAgentBase = "github-cli"
UserAgentBase is the honest half of the User-Agent. The version is appended at runtime. It is deliberately not configurable: making it configurable would be making impersonation a feature, and a tool that reads only public pages has no reason to hide.
Variables ¶
var DefaultFollow = []string{PredPartOf, PredOwnedBy, PredForkOf, PredHasTopic, PredAuthoredBy}
DefaultFollow is the crawler's follow set. It deliberately excludes references, starredBy, follows, dependsOn, and usedBy: those five turn a bounded walk into an unbounded one, and each has to be asked for by name.
var Defaults = Config{ Rate: 125 * time.Millisecond, Retries: 4, Workers: 4, Timeout: 30 * time.Second, }
Defaults are the pacing numbers every command starts from. GitHub publishes no rate limit for the pages, so these are chosen to be quieter than a person browsing with a few tabs open: eight requests a second across four workers.
var Kinds = []string{ KindRepo, KindUser, KindOrg, KindIssue, KindPR, KindDiscussion, KindCommit, KindBranch, KindTag, KindRelease, KindFile, KindTree, KindLabel, KindMilestone, KindTopic, KindGist, KindPackage, KindAction, KindWiki, KindAdvisory, KindCompare, KindContributor, KindContribution, KindEvent, }
Kinds is the whole set, in the order above, for help text and for the error a bad kind produces. Listing them is the difference between an error a reader can act on and one that sends them to the source.
var LiteralPredicates = map[string]bool{ PredWrittenIn: true, PredLicensedUnder: true, PredReactedWith: true, }
LiteralPredicates are the two whose object is a bare string rather than a URI, because a language and a licence are not github.com entities. RDF gives them synthetic IRIs in the gh: namespace; `github edges` prints them as they are written on the page.
var RDFFormats = []string{FormatNT, FormatNQuads, FormatTurtle, FormatJSONLD}
RDFFormats is the accepted set, for help text and validation.
var Routes = []RouteInfo{
{"/{owner}/{repo}", "embedded", "route-json", "sidebarAbout lives only in the HTML payload"},
{"/{owner}/{repo}/tree/{ref}/{path}", "route-json", "embedded", ""},
{"/{owner}/{repo}/blob/{ref}/{path}", "embedded", "raw", "the route JSON dropped the metadata block, so the page is the read; bytes from raw"},
{"/{owner}/{repo}/branches", "route-json", "xhr", ""},
{"/{owner}/{repo}/refs", "xhr", "git", "names only, 6 KB against 588 KB"},
{"/{owner}/{repo}/commits/{ref}", "route-json", "feed", ""},
{"/{owner}/{repo}/commit/{sha}", "route-json", "raw", "the diff comes from .patch"},
{"/{owner}/{repo}/compare/{a}...{b}", "route-json", "raw", ""},
{"/{owner}/{repo}/issues/{n}", "embedded", "ld-json", "Relay preloaded queries"},
{"/{owner}/{repo}/pull/{n}", "embedded", "raw", "Relay, plus .diff and .patch"},
{"/{owner}/{repo}/discussions/{n}", "embedded", "", "Relay"},
{"/{owner}/{repo}/issues", "search", "", "type=issues with a repo: qualifier"},
{"/{owner}/{repo}/pulls", "search", "", "type=pullrequests"},
{"/{owner}/{repo}/releases", "feed", "html", "releases.atom, then a page each for assets"},
{"/{owner}/{repo}/releases/tag/{tag}", "html", "feed", "download counts exist nowhere else"},
{"/{owner}/{repo}/tags", "feed", "git", "the feed is recent, git is complete"},
{"/{owner}/{repo}/graphs/contributors", "xhr", "", "answers 202 while it computes, so it polls"},
{"/{owner}/{repo}/wiki", "feed", "html", ""},
{"/{owner}/{repo}.git/info/refs", "git", "", "every ref and its SHA in one request"},
{"/{login}", "html", "ld-json", "microdata and microformats, no payload at all"},
{"/{org}", "html", "xhr", "two deferred fragments under --deep"},
{"/{login}?tab=repositories", "search", "html", "user: qualifier"},
{"/{login}?tab=stars", "html", "", ""},
{"/{login}.atom", "feed", "", ""},
{"/users/{login}/hovercard", "xhr", "html", ""},
{"/users/{login}/contributions", "xhr", "", "returns an HTML fragment, not JSON"},
{"/orgs/{org}/people", "html", "", ""},
{"/search", "search", "", "ten types, code is the one that needs a token"},
{"/trending", "html", "", "no JSON equivalent exists, tokened or not"},
{"/topics/{slug}", "html", "search", ""},
{"gist.github.com/{id}", "html", "raw", ""},
{"raw.githubusercontent.com/...", "raw", "", ""},
{"codeload.github.com/...", "raw", "", ""},
}
Routes is the whole index. Surface names are the ones in doc 01: html, route-json, xhr, search, feed, raw, git, embedded, ld-json.
var SearchTypes = []string{ SearchRepos, SearchIssues, SearchPulls, SearchUsers, SearchCommits, SearchDiscussions, SearchTopics, SearchPackages, SearchWikis, SearchMarket, }
SearchTypes is every type in the order the commands present them.
var SocialPredicates = []string{PredStarredBy, PredFollows, PredSponsors, PredReactedWith}
SocialPredicates are the ones a command has to be asked for by name.
var TrustLevels = []string{SrcID, SrcPayload, SrcFeed, SrcHTML, SrcText}
TrustLevels is the accepted set, for help text and for validation.
Functions ¶
func Classify ¶
Classify turns anything a person might paste into a kind and an id. It does no I/O, and it never fails on a well-formed github.com URL.
Two of its answers are guesses and both are documented as such. A bare word is a user, because a pure function cannot tell a user from an organization without asking. A bare owner/name is a repository. `github get` reads the page and returns a record whose Kind is the truth; classification is a routing hint, not an answer.
func DefaultCacheDir ¶
func DefaultCacheDir() string
DefaultCacheDir is $XDG_CACHE_HOME/github-cli, or the platform equivalent.
func DomainDefaults ¶
DomainDefaults overlays this site's baseline onto the framework's. GitHub publishes no rate limit for the pages, so these are chosen to be quieter than a person browsing with a few tabs open: eight requests a second across four workers.
func Extract ¶
Extract turns one record into its node, its edges, and its facts. A record kind it does not know produces an empty node, which every caller treats as nothing to say rather than as an error.
func IRI ¶
IRI maps a github:// URI to its dereferenceable https form. The github:// form stays on the record plane, where it is a stable key rather than a location.
The three derived kinds have no address of their own, so they map into the gh: namespace instead of pretending to be a page.
func Locate ¶
Locate turns a kind and id back into the canonical github.com URL. Locate(Classify(u)) is the canonical form of u, which is what makes -o url safe to pipe back into the tool.
func Page ¶
Page builds a github.com URL from path segments, escaping each one.
It really does escape now. It used to copy the segments across untouched while the comment said otherwise, which is the kind of helper that works until the first branch with a space in its name.
func RepoOf ¶
RepoOf returns the repository an id belongs to, for the kinds whose id carries one. This is what makes `github tree <a file URL>` work.
func ResolveRef ¶
ResolveRef resolves any accepted reference to the id for the kind a command names.
Classify has two guesses in it: one bare word is a user, and one bare owner/name is a repository. Those are guesses because a pure function cannot tell a user from an organization or a repository from anything else without asking, so a guess yields to the command that names its own kind while an explicit URL or URI does not. That is what makes `github org golang` work and `github org https://github.com/golang/go` fail with a message that says why.
func ResolveRepo ¶
ResolveRepo resolves any reference to the repository it belongs to. Commands that work on a repository use it so that a pasted file URL, issue URL, or release URL all name the repository they are part of, which is what makes `github commits <any URL from the repo>` work.
func SortEdges ¶
func SortEdges(edges []Edge)
SortEdges gives an export a stable order, which is what makes a diff of two runs readable.
func SplitPathID ¶
SplitPathID splits owner/name@ref/path/to/file into its three parts. The ref runs to the next slash, which means a branch with a slash in its name (feature/x) parses as ref "feature" and path "x/...". That is a real ambiguity in GitHub's own URLs and nothing here can resolve it; a caller who knows better passes --ref.
func SplitThreadID ¶
SplitThreadID splits owner/name#123.
func TrustAtLeast ¶
TrustAtLeast reports whether a source meets a floor. An unknown floor lets everything through rather than silently dropping the whole graph, and an unknown source is treated as the weakest thing there is.
Types ¶
type Account ¶
type Account struct {
Base
Login string `json:"login" table:"login"`
Name string `json:"name,omitempty" table:"name"`
// Type is User or Organization, decided by which blocks the page carries
// rather than guessed from the login.
Type string `json:"type" table:"type"`
Bio string `json:"bio,omitempty" table:"bio,truncate"`
Company string `json:"company,omitempty" table:"company"`
Location string `json:"location,omitempty" table:"location"`
Website string `json:"website,omitempty" table:"-"`
Email string `json:"email,omitempty" table:"-"`
Pronouns string `json:"pronouns,omitempty" table:"-"`
SocialLinks []string `json:"social_links,omitempty" table:"-"`
DatabaseID *int `json:"database_id,omitempty" table:"-"`
NodeID string `json:"node_id,omitempty" table:"-"`
AvatarURL string `json:"avatar_url,omitempty" table:"-"`
Followers *int `json:"followers,omitempty" table:"followers"`
FollowersDisplay string `json:"followers_display,omitempty" table:"-"`
Following *int `json:"following,omitempty" table:"following"`
Starred *int `json:"starred,omitempty" table:"-"`
RepoCount *int `json:"repo_count,omitempty" table:"repos"`
// These three come from the navigation tabs, which is the only place
// either template states them. A tab with nothing in it carries no counter
// at all, so zero and absent are the same thing here and the field stays
// nil rather than claiming a zero it did not read.
PackageCount *int `json:"package_count,omitempty" table:"-"`
ProjectCount *int `json:"project_count,omitempty" table:"-"`
SponsoringCount *int `json:"sponsoring_count,omitempty" table:"-"`
CreatedAt *time.Time `json:"created_at,omitempty" table:"joined,time"`
Sponsorable bool `json:"sponsorable" table:"-"`
IsVerified bool `json:"is_verified" table:"-"`
IsHireable bool `json:"is_hireable" table:"-"`
ReadmeHTML string `json:"readme_html,omitempty" table:"-"`
ReadmeText string `json:"readme_text,omitempty" table:"-"`
PinnedRepos []string `json:"pinned_repos,omitempty" table:"-"`
Organizations []string `json:"organizations,omitempty" table:"-"`
Achievements []string `json:"achievements,omitempty" table:"-"`
SocialImageURL string `json:"social_image_url,omitempty" table:"-"`
}
Account is a user or an organization. Profiles carry no JSON payload at all, so every field here comes from microdata, a microformat class, a stable data attribute, or a counted link. That makes accounts the most selector-dependent records in the tool, and the reason every field has a golden pinning it.
type Action ¶
type Action struct {
Base
Name string `json:"name" table:"name"`
Slug string `json:"slug" table:"-"`
Owner string `json:"owner,omitempty" table:"owner"`
Description string `json:"description,omitempty" table:"description,truncate"`
// ShortDescription is the one-line blurb on the listing card, which is a
// different string from Description on an app and the same one on a
// repository action. Both are kept rather than picked between.
ShortDescription string `json:"short_description,omitempty" table:"-"`
FullDescription string `json:"full_description,omitempty" table:"-"`
ExtendedDescription string `json:"extended_description,omitempty" table:"-"`
Type string `json:"type,omitempty" table:"type"`
PrimaryCategory string `json:"primary_category,omitempty" table:"category"`
SecondaryCategory string `json:"secondary_category,omitempty" table:"-"`
Highlights []string `json:"highlights,omitempty" table:"-"`
// A repository action and a marketplace app are both listings and the
// search results are interleaved, but only one of these two groups is ever
// populated for a given record. Which group is filled in is itself the
// answer to "what kind of thing is this".
Path string `json:"path,omitempty" table:"-"`
RepositoryID *int `json:"repository_id,omitempty" table:"-"`
IconName string `json:"icon_name,omitempty" table:"-"`
IconColor string `json:"icon_color,omitempty" table:"-"`
ListingID *int `json:"listing_id,omitempty" table:"-"`
LogoURL string `json:"logo_url,omitempty" table:"-"`
InstallationCount *int `json:"installation_count,omitempty" table:"installs"`
State string `json:"state,omitempty" table:"state"`
CompanyURL string `json:"company_url,omitempty" table:"-"`
DocumentationURL string `json:"documentation_url,omitempty" table:"-"`
SupportURL string `json:"support_url,omitempty" table:"-"`
PrivacyPolicyURL string `json:"privacy_policy_url,omitempty" table:"-"`
TermsURL string `json:"terms_url,omitempty" table:"-"`
PricingURL string `json:"pricing_url,omitempty" table:"-"`
Stars *int `json:"stars,omitempty" table:"stars"`
DependentCount *int `json:"dependent_count,omitempty" table:"used_by"`
IsFree bool `json:"is_free" table:"-"`
IsVerifiedOwner bool `json:"is_verified_owner" table:"verified"`
IsFeatured bool `json:"is_featured" table:"-"`
IsRecommended bool `json:"is_recommended" table:"-"`
ByGitHub bool `json:"by_github" table:"-"`
}
Action is a marketplace listing. The type covers both actions and apps, and Type says which.
type Actor ¶
type Actor struct {
Login string `json:"login" table:"login"`
Name string `json:"name,omitempty" table:"name"`
Type string `json:"type,omitempty" table:"-"`
NodeID string `json:"node_id,omitempty" table:"-"`
DatabaseID *int `json:"database_id,omitempty" table:"-"`
AvatarURL string `json:"avatar_url,omitempty" table:"-"`
URL string `json:"url,omitempty" table:"-"`
URI string `json:"uri,omitempty" table:"-"`
}
Actor is a person or an organization as it appears inside another record: on a commit, an issue, a release. It is deliberately small. The full account is a separate read, and inlining it would turn one request into hundreds. Both id forms are kept. The numeric one is what avatar URLs and the older links use, the base64 global one is what Relay results carry, and a joiner downstream will have one or the other and not both.
func (Actor) String ¶
String is the login, which is what an author column in a table is for. The renderer would otherwise fall back to JSON for a struct field, and a cell holding the whole actor is wide enough to push every other column off the screen. The full thing is still there in every other format.
type Asset ¶
type Asset struct {
Name string `json:"name" table:"name"`
// Label is the text GitHub prints in place of the filename, "GitHub CLI
// 2.63.2 checksums" for gh_2.63.2_checksums.txt. It is set per asset at
// upload time and is usually the only human-readable thing in the row.
Label string `json:"label,omitempty" table:"-"`
Size *int64 `json:"size,omitempty" table:"-"`
SizeDisplay string `json:"size_display,omitempty" table:"size"`
// Digest is the sha256 the assets fragment publishes, prefixed "sha256:".
// It is new: GitHub added it around the time it stopped showing download
// counts to logged-out clients, so this record trades a popularity number
// for something you can actually verify a download against.
Digest string `json:"digest,omitempty" table:"-"`
// DownloadCount is left absent on a keyless read. The release page used to
// print it next to each asset and no longer does, and no other public
// surface carries it. The field stays because the shape of the record
// should not change when GitHub changes its mind again.
DownloadCount *int `json:"download_count,omitempty" table:"-"`
URL string `json:"url" table:"url,url"`
UpdatedAt *time.Time `json:"updated_at,omitempty" table:"-"`
ContentType string `json:"content_type,omitempty" table:"-"`
}
Asset is one release download.
type Base ¶
type Base struct {
Kind string `json:"kind" table:"kind"`
ID string `json:"id" table:"id" kit:"id"`
URI string `json:"uri,omitempty" table:"-"`
URL string `json:"url,omitempty" table:"-,url"`
Sources []string `json:"sources,omitempty" table:"-"`
Via map[string]string `json:"via,omitempty" table:"-"`
Extra json.RawMessage `json:"extra,omitempty" table:"-"`
}
Base is embedded in every record. Kind and ID are the identity, URI and URL are the two addresses, Sources records which pages were read, Via records which extraction tier produced a field, and Extra is the data-loss guard.
Via is deliberately not part of Extra. Extra means "GitHub sent this and nobody modelled it" and the suite asserts it is empty; provenance we wrote ourselves has no business making that assertion fail.
type BlobOptions ¶
type BlobOptions struct {
Ref string
// Content fetches the bytes from raw.githubusercontent.com, one extra
// request. Without it the record is metadata only.
Content bool
// Styled fetches the page HTML for rawLines and the syntax highlighting
// spans that run parallel to them. It is expensive and only a consumer that
// re-renders the file wants it.
Styled bool
}
BlobOptions controls a file read.
type Check ¶
type Check struct {
Name string `json:"name" table:"check"`
Status string `json:"status" table:"status"`
Detail string `json:"detail" table:"detail"`
}
Check is one diagnostic.
type Client ¶
type Client struct {
HTTP *http.Client
UserAgent string
Rate time.Duration // the minimum gap between requests, shared by every worker
Retries int
Workers int
CacheDir string
NoCache bool
CacheTTL time.Duration
// Deep makes a record read follow the extra requests that fill in fields
// the primary surface omits. Off by default because it multiplies requests.
Deep bool
// Verbose writes one line per request to stderr: the URL, the surface, the
// status, and the byte count. It is the fastest way to see what a command
// actually costs.
Verbose bool
// contains filtered or unexported fields
}
Client reads github.com. It is safe for concurrent use: the pacer and the cache are synchronised, so a crawl running many workers still produces one polite stream of requests rather than one stream per worker.
There is no Token field. That is not an oversight, it is the design: see the package comment, and see TestNoAuth, which fails the build if a credential ever appears in this package.
func NewClient ¶
NewClient returns a client configured from cfg, falling back to Defaults for anything unset.
func (*Client) Account ¶
Account reads a user or organization profile. The concrete type is decided by the page, so a caller that does not know which it has can just call this.
func (*Client) Activity ¶
Activity reads a public event stream. The same feed shape serves an account and a repository, so the argument is either a login or owner/name and the URL is the only thing that differs.
This replaces the REST events endpoint outright. The feed is public, cheap, and needs no credential, and the event class is encoded in each entry's id, so the type comes from a field rather than from matching on prose.
func (*Client) Archive ¶
Archive streams a repository tarball or zipball from codeload. The caller closes the reader. Nothing here is cached or buffered: an archive is measured in tens of megabytes and belongs on a disk, not in a map.
format is tar.gz or zip. ref may be a branch, a tag, or a SHA, and the three take different codeload paths, which is what refPath sorts out.
func (*Client) Branches ¶
func (c *Client) Branches(ctx context.Context, repo string, opts RefOptions, emit func(GitRef) error) error
Branches lists branches. Without Complete this is the branches route, which carries the last author and the last authored date and is capped by GitHub; with it, this is the git advertisement, which is complete and carries SHAs.
Neither is strictly better and the record says which one answered, so a consumer that finds Author empty knows why.
func (*Client) CommitInfo ¶
func (c *Client) CommitInfo(ctx context.Context, repo, sha string, opts CommitInfoOptions) (*Commit, error)
CommitInfo reads one commit. sha may be a full SHA, an abbreviation, a branch, or a tag: the route resolves all four, and the record reports what it resolved to.
func (*Client) Commits ¶
func (c *Client) Commits(ctx context.Context, repo string, opts CommitOptions, emit func(Commit) error) error
Commits streams a repository's history, newest first.
The route groups commits under calendar-day headings and this flattens them, keeping the heading on each record as DateGroup. That heading is the only place the surface says which timezone it grouped in, so throwing it away would make an off-by-one-day question unanswerable.
func (*Client) CompareRefs ¶
func (c *Client) CompareRefs(ctx context.Context, repo, base, head string, opts CompareOptions) (*Compare, error)
CompareRefs reads the range between two refs.
It reads the plain-text patch mailbox, not the compare page. The page has no JSON payload of any kind, it is more than twice the size, and every field on it is a class name away from breaking. git-format-patch output is a format GitHub does not own and cannot restyle, and it carries every commit with its author, date, subject, and diff.
The trade is that the mailbox has no logins, only names and emails, so the authors on these commits have Name set and Login empty. That is honest: the patch really does not say who the GitHub user was.
func (*Client) Contributions ¶
func (c *Client) Contributions(ctx context.Context, login string, year int, emit func(ContributionDay) error) error
Contributions reads a year of a profile's contribution graph, one record per day. This is the only representation of the numbers that exists without a token: the GraphQL field that carries them refuses anonymous callers.
A year is the largest window the fragment serves. Asking for a wider range gets the last year, so the range is stated rather than inferred.
func (*Client) Contributors ¶
func (c *Client) Contributors(ctx context.Context, repo string, opts ContributorOptions, emit func(Contributor) error) error
Contributors reads the contributor graph's own data route.
The route answers 202 with an empty body while GitHub computes the numbers, which is normal rather than an error and is why this polls. A large repository takes a few seconds the first time and is instant afterwards.
func (*Client) Crawl ¶
Crawl walks outward from a seed reference. Nodes, edges, and facts come out as they are discovered, and the walk stops cleanly at either bound and reports what it had rather than failing.
func (*Client) DefaultBranch ¶
DefaultBranch asks the git advertisement which branch HEAD points at. It is the authoritative answer, where the route JSON's refInfo is whichever ref the URL happened to resolve to.
func (*Client) Dependencies ¶
func (c *Client) Dependencies(ctx context.Context, repo string, limit int, emit func(Dependency) error) error
Dependencies lists what a repository declares in its manifests.
A repository with the dependency graph switched off answers with a page and no rows, which is an empty list rather than an error: the difference between "nothing to report" and "not enabled" is not on the page, so claiming to know which one it is would be making it up.
func (*Client) Dependents ¶
func (c *Client) Dependents(ctx context.Context, repo string, limit int, emit func(Dependent) error) error
Dependents lists the repositories that depend on this one.
The list is ordered by stars and it is long: a popular library has tens of thousands of rows at thirty a page, so --limit is the flag that matters here and the walk stops the moment it is reached.
func (*Client) Diff ¶
Diff returns the unified diff, which is the patch without the commit metadata. On a wide range it is a third of the size.
func (*Client) Discussion ¶
Discussion reads one discussion.
Discussions never migrated to React, so there is no payload to decode. What there is instead is a schema.org QAPage block, which carries the body, the upvote count, and the accepted answer, and is the most reliable thing on the page because GitHub publishes it for search engines and therefore keeps it working. Everything the block does not have comes from the markup.
repo may be owner/name or an organization login: organization-level discussions live at /orgs/{login}/discussions/{n} and the read follows the redirect either way, then takes the true repository off the sidebar.
func (*Client) Doctor ¶
Doctor runs the checks in order and emits one record each. It stops for nothing: a failed reachability check makes the page check fail too, and seeing both is more useful than seeing the first one alone.
func (*Client) Download ¶
Download streams one file's bytes to w and reports how many it wrote.
It exists next to Raw because the two have different costs. Raw buffers, so a caller who wants to look at the bytes gets them in one piece and gets the cache; Download does not buffer and does not cache, so a caller piping a hundred-megabyte binary through to a disk pays for none of it.
func (*Client) Estimate ¶
Estimate reads the seed and reports what one more level would cost. It is deliberately a lower bound and the note says so: the first level is countable because the seed's edges are in hand, and everything past it depends on a branching factor that cannot be seen from here without doing the walk.
func (*Client) Followers ¶
func (c *Client) Followers(ctx context.Context, login string, limit int, emit func(Account) error) error
Followers lists the accounts following a login, newest first, which is the order the page uses and the only order it offers.
func (*Client) Following ¶
func (c *Client) Following(ctx context.Context, login string, limit int, emit func(Account) error) error
Following lists the accounts a login follows.
func (*Client) Forks ¶
Forks lists the public forks of a repository. The page is the only keyless source: the network graph route needs a session and the search index does not model the parent link.
func (*Client) Get ¶
Get fetches a URL on a surface. Every request in the package goes through here, so pacing, caching, retry, and error classification each have exactly one home.
func (*Client) Gist ¶
Gist reads one gist and its file list. Contents are a second request per file and are opt-in, because a gist can hold a megabyte of log paste.
func (*Client) GraphOf ¶
GraphOf builds the node, edges, and facts for one entity. `github graph`, `github edges`, and `github rdf` all call it, so the three never disagree about what an entity's edges are.
func (*Client) GraphOfRef ¶
GraphOfRef is GraphOf for a reference that has not been classified yet, and it reports the kind it turned out to be so a caller can say what it read.
func (*Client) Head ¶
Head issues a HEAD and returns the response headers. It exists for one question, "how big is this file", which is worth asking without downloading the answer.
func (*Client) Issue ¶
Issue reads one issue. repo is owner/name.
A pull request number handed to this function does not 404: GitHub redirects /issues/{n} to /pull/{n}, and the read follows that redirect and hands off to PullRequest rather than returning a half-decoded record.
func (*Client) Languages ¶
Languages reports the language histogram as one record per language. The numbers are on the repository record already; this exists because "what is this written in, in what proportion" is a question worth one command rather than a field selector on another one. Languages reports the language breakdown, largest first.
This reads the sidebar fragment rather than a whole repository page, because the fragment is where the numbers are and it is 3 KB where the page is 300. The numbers are percentages: GitHub computes byte counts and publishes only the proportions, so a byte count is not something this can report honestly.
func (*Client) Members ¶
func (c *Client) Members(ctx context.Context, login string, limit int, emit func(Account) error) error
Members lists an organization's public members. The roster is at /orgs/{login}/people rather than on the profile, and the profile's avatar strip is a sample of it rather than a short version of it.
func (*Client) Org ¶
Org reads an organization. It is an error to point this at a user, because a caller that asked for an organization and silently got a user back will not notice until something downstream is confusing.
func (*Client) Page ¶
Page fetches a URL and hands back the whole extraction, nothing dropped. Every reader in the package works from this, and `github page` prints it, which is what makes the debugging tool show the same view the readers see rather than a second opinion about the page.
The URL is taken as given rather than resolved from an entity, because the pages worth inspecting most are the ones the model does not cover yet.
func (*Client) Patch ¶
Patch returns the git patch for a commit, a pull request, or a range. url is any github.com URL naming one of the three; the .patch suffix is appended.
This is the cheapest complete view of a change on the whole site: no negotiation, no payload, no page, and no token.
func (*Client) Poll ¶
Poll waits for a statistic GitHub computes in the background. Only the contributor graph needs it: the first request kicks off the job and answers 202 with an empty body, and the answer arrives some seconds later.
Observed twice in a row several seconds apart on a cold repository, so one retry is not enough. Eight attempts over a sixty second budget, and never a silent empty list: "no contributors" and "not computed yet" are different answers and conflating them would make an empty result look authoritative.
func (*Client) PullCommits ¶
func (c *Client) PullCommits(ctx context.Context, repo string, number int, limit int, emit func(Commit) error) error
PullCommits emits the commits on a pull request. The route serves them all in one response grouped by push, which is why there is no pager here.
func (*Client) PullRequest ¶
PullRequest reads one pull request.
This one takes the HTML page rather than the route JSON, and the reason is worth writing down. Asking a React route for JSON returns the props for that route only, as a delta against the layout that is already mounted in a real browser. /pull/{n} with Accept: application/json is a kilobyte of websocket channel tokens: no title, no author, no merge state. The layout route that holds those lives in the page's embedded payload and nowhere else, so the page is what we fetch. It is about 165 KB on the wire and that is the price.
One fetch then covers everything a logged-out client can see:
pullRequestsLayoutRoute title, author, refs, head sha, merge metadata pullRequestsConversationsRoute node id and the lock flag the sidebar markup labels og:description the body, truncated to 200 characters
The body really is a snippet. GitHub renders the conversation client-side and serves none of it to a logged-out client, so 200 characters of open graph text is the whole of what is reachable. _via records body: og for it, and the hovercard fragment is the fallback when the page has no open graph body.
func (*Client) Raw ¶
Raw returns the bytes of a file. One request to raw.githubusercontent.com, no page, no negotiation, and the same contract for a binary as for text.
func (*Client) Refs ¶
func (c *Client) Refs(ctx context.Context, repo string, opts RefOptions, emit func(GitRef) error) error
Refs lists every ref of every kind in one request.
func (*Client) Release ¶
func (c *Client) Release(ctx context.Context, repo, tag string, opts ReleaseOptions) (*Release, error)
Release reads one release by tag. tag may also be "latest", which github.com redirects to whatever that is today.
The per-tag page is not the list page with nine releases removed. It is a different template with a different shape, so it gets its own decoder rather than a selector that limps along on both. What it gains over a list entry is the commit the tag points at; what it lacks is nothing.
func (*Client) Releases ¶
func (c *Client) Releases(ctx context.Context, repo string, opts ReleaseOptions, emit func(Release) error) error
Releases streams a repository's releases, newest first.
The list comes from the HTML pages rather than releases.atom, which is the opposite of what you would expect from a feed-shaped problem. The feed gives ten entries and does not page, so it cannot answer "every release"; the pages give ten at a time with a rel="next" and carry the Latest and Pre-release labels the feed has no room for.
func (*Client) ReposAsShown ¶
func (c *Client) ReposAsShown(ctx context.Context, login string, limit int, emit func(Repo) error) error
ReposAsShown lists an account's repositories in the order and with the filters the profile tab itself uses. `github repos --user x` runs a search instead, which sorts and pages better; this is what --as-shown selects when the exact page order is the point.
func (*Client) SearchAccounts ¶
func (c *Client) SearchAccounts(ctx context.Context, query string, limit int, emit func(Account) error) error
SearchAccounts streams user records.
func (*Client) SearchCodeBy ¶
SearchCodeBy is the honest gap. The route answers 200 with zero results without a session, which is the worst possible failure mode: it looks like the query matched nothing.
func (*Client) SearchCommitsBy ¶
func (c *Client) SearchCommitsBy(ctx context.Context, query string, limit int, emit func(Commit) error) error
SearchCommitsBy streams commit records. Commit search is the only source for signature and verification state on a keyless surface.
func (*Client) SearchDiscussionsBy ¶
func (c *Client) SearchDiscussionsBy(ctx context.Context, query string, limit int, emit func(Discussion) error) error
SearchDiscussionsBy streams discussion records.
func (*Client) SearchIssuesAndPulls ¶
func (c *Client) SearchIssuesAndPulls(ctx context.Context, query, typ string, limit int, emit func(Thread) error) error
SearchIssuesAndPulls streams thread records. typ is SearchIssues or SearchPulls: the result shape is identical and only the qualifier differs, which is why one decoder serves both.
func (*Client) SearchMarketplace ¶
func (c *Client) SearchMarketplace(ctx context.Context, query string, limit int, emit func(Action) error) error
SearchMarketplace streams action and app listings.
func (*Client) SearchPackagesBy ¶
func (c *Client) SearchPackagesBy(ctx context.Context, query string, limit int, emit func(Package) error) error
SearchPackagesBy streams package records. Search is the only source for packages, so these records are complete rather than thin.
func (*Client) SearchRepositories ¶
func (c *Client) SearchRepositories(ctx context.Context, query string, limit int, emit func(Repo) error) error
SearchRepositories streams repository records for a query.
func (*Client) SearchTopicsBy ¶
func (c *Client) SearchTopicsBy(ctx context.Context, query string, limit int, emit func(Topic) error) error
SearchTopicsBy streams topic records.
func (*Client) SearchWikisBy ¶
func (c *Client) SearchWikisBy(ctx context.Context, query string, limit int, emit func(WikiPage) error) error
SearchWikisBy streams wiki page records.
func (*Client) Starred ¶
Starred lists the repositories an account has starred. It is a different template from the repositories tab, so it gets its own row reader even though the two records are the same shape.
func (*Client) Stats ¶
Stats is the counts in one record. Everything in it is already on the repository record; the point is a record with nothing else in it, so `github stats x -o json` is a thing you can diff week to week.
func (*Client) Stream ¶
Stream opens a body without buffering, retrying, or caching. Release assets and repository archives go through here: a tarball does not belong in memory and does not belong in the cache. The caller closes the reader.
func (*Client) Tags ¶
func (c *Client) Tags(ctx context.Context, repo string, opts RefOptions, emit func(GitRef) error) error
Tags lists tags. The git advertisement is the default here rather than an opt-in, because the tags feed gives ten and the tags page gives ten at a time, and a repository with four hundred tags is the normal case.
func (*Client) Timeline ¶
func (c *Client) Timeline(ctx context.Context, repo string, number int, limit int, emit func(TimelineItem) error) error
Timeline emits the events the issue page carries.
It is deliberately not a pager. The page preloads the first fifteen events and, on a long thread, the last few; walking past those needs the GraphQL endpoint, which needs a session. So this returns what the page had and Truncated says whether there is more, rather than pretending to a completeness it cannot deliver.
func (*Client) TopicPage ¶
TopicPage reads one topic. The search result for a topic carries the name and a short blurb; the page carries the long description, the logo, who created the thing, when it was released, the Wikipedia link, and the related topics, which is most of what makes a topic worth having a record for.
func (*Client) Tree ¶
func (c *Client) Tree(ctx context.Context, repo, path string, opts TreeOptions, emit func(TreeEntry) error) error
Tree emits the entries of one directory, or of a whole subtree under Recursive. path is repository-relative and empty means the root.
Entries are emitted as each directory arrives rather than collected, so a recursive walk of a large repository starts printing immediately.
func (*Client) Trending ¶
func (c *Client) Trending(ctx context.Context, opts TrendingOptions, emit func(Trending) error) error
Trending lists the trending repositories. Rank is the position on the page, which is the only ordering the surface has and is worth keeping, since the list has no other stable key.
func (*Client) TrendingDevelopers ¶
func (c *Client) TrendingDevelopers(ctx context.Context, opts TrendingOptions, emit func(Account) error) error
TrendingDevelopers lists the trending developers, each with the repository the page picked out for them.
func (*Client) VerifyCommits ¶
VerifyCommits fills in signature state, which no commit route carries.
Commit search is the only keyless surface that reports it, so this asks search for the exact SHAs and merges what comes back. It is a separate function rather than a flag because it is a different request against a different index, and a caller should see that in the code they wrote.
type Commit ¶
type Commit struct {
Base
Repo string `json:"repo" table:"-"`
SHA string `json:"sha" table:"sha"`
NodeID string `json:"node_id,omitempty" table:"-"`
Subject string `json:"subject" table:"subject,truncate"`
SubjectHighlight string `json:"subject_highlight,omitempty" table:"-"`
Body string `json:"body,omitempty" table:"-"`
BodyHTML string `json:"body_html,omitempty" table:"-"`
Authors []Actor `json:"authors,omitempty" table:"authors"`
Committer *Actor `json:"committer,omitempty" table:"-"`
Pusher *Actor `json:"pusher,omitempty" table:"-"`
AuthoredAt *time.Time `json:"authored_at,omitempty" table:"authored,time"`
CommittedAt *time.Time `json:"committed_at,omitempty" table:"-"`
PushedAt *time.Time `json:"pushed_at,omitempty" table:"-"`
// DateGroup is the calendar-day heading the commit list grouped this commit
// under. It is kept because it is the only place the surface tells you what
// timezone it grouped in.
DateGroup string `json:"date_group,omitempty" table:"-"`
Verification string `json:"verification,omitempty" table:"-"`
// VerificationReason is the why behind Verification: "unsigned",
// "valid", "expired_key", and so on. Verification alone says a commit is
// unverified without saying whether that is because nobody signed it or
// because the signature failed, which are very different facts.
VerificationReason string `json:"verification_reason,omitempty" table:"-"`
SignedByGitHub bool `json:"signed_by_github" table:"-"`
HasSignature bool `json:"has_signature" table:"-"`
KeyID string `json:"key_id,omitempty" table:"-"`
KeyExpired bool `json:"key_expired" table:"-"`
StatusRollup string `json:"status_rollup,omitempty" table:"status"`
StatusSummary string `json:"status_summary,omitempty" table:"-"`
CommentCount *int `json:"comment_count,omitempty" table:"-"`
// IssueRefs are the issues and pull requests this commit's message closes
// or mentions, already resolved by GitHub. This is the commit-to-thread
// edge of the graph, handed over for free, and it is the reason commit
// search is worth reading even when you already have the commit.
IssueRefs []ThreadRef `json:"issue_refs,omitempty" table:"-"`
Parents []string `json:"parents,omitempty" table:"-"`
Additions *int `json:"additions,omitempty" table:"-"`
Deletions *int `json:"deletions,omitempty" table:"-"`
Files []FileChange `json:"files,omitempty" table:"-"`
}
Commit is one commit. Authors is a list because co-authored commits are common and the payload already ships an array; Committer is separate and only differs from the author when the surface says it does.
type CommitInfoOptions ¶
type CommitInfoOptions struct {
// Files fills the per-file change list from the inline diff. The route
// ships the whole thing whether we decode it or not, so this costs parsing
// and memory rather than a request.
Files bool
// Patch fetches the .patch form as well, one extra request, for a caller
// that wants to apply the change rather than describe it.
Patch bool
}
CommitInfoOptions controls how much of a commit's diff comes back.
type CommitOptions ¶
type CommitOptions struct {
// Ref is a branch, a tag, or a SHA. Empty means the default branch.
Ref string
// Path limits history to one file or directory.
Path string
// Author is a login, not an email.
Author string
// Since and Until are dates, YYYY-MM-DD.
Since string
Until string
// Limit stops the walk. Zero means every commit, which on a large
// repository is thousands of requests, so callers should set it.
Limit int
}
CommitOptions controls a commit walk. Every field maps to a query parameter the route already understands, so filtering happens on GitHub's side and not after a full download.
type Compare ¶
type Compare struct {
Base
Repo string `json:"repo" table:"-"`
BaseRef string `json:"base_ref" table:"base"`
HeadRef string `json:"head_ref" table:"head"`
PatchURL string `json:"patch_url" table:"-"`
DiffURL string `json:"diff_url" table:"-"`
Commits []Commit `json:"commits,omitempty" table:"-"`
// CommitCount is len(Commits) and is here so a table row says something
// useful without the caller reaching into the slice.
CommitCount int `json:"commit_count" table:"commits"`
Files []FileChange `json:"files,omitempty" table:"-"`
FileCount int `json:"file_count" table:"files"`
Additions int `json:"additions" table:"+"`
Deletions int `json:"deletions" table:"-"`
// Patch is the raw stream, kept only when the caller asked for it. It is
// megabytes on a wide range.
Patch string `json:"patch,omitempty" table:"-"`
}
Compare is a range between two commits: what `github compare` returns.
It is built from the plain-text patch mailbox rather than the compare page. The page is 227 KB of HTML with no JSON payload at all, and the .patch suffix on the same range is a git-format-patch stream that carries every commit's author, date, subject, and diff with no markup to guess at. Parsing a format GitHub cannot restyle is the whole point.
type CompareOptions ¶
type CompareOptions struct {
// Files parses the per-file changes out of the patch. Free, since the
// patch is already downloaded.
Files bool
// Patch keeps the raw stream on the record.
Patch bool
}
CompareOptions controls a range read.
type Config ¶
type Config struct {
UserAgent string
Rate time.Duration
Retries int
Workers int
Timeout time.Duration
CacheDir string
NoCache bool
CacheTTL time.Duration
Deep bool
}
Config is the resolved per-run configuration. It carries no credential field because there is no credential.
type ContributionDay ¶
type ContributionDay struct {
Base
Login string `json:"login" table:"login"`
Date time.Time `json:"date" table:"date,time"`
Count int `json:"count" table:"count"`
Level int `json:"level" table:"level"`
}
ContributionDay is one square of a profile contribution graph.
type Contributor ¶
type Contributor struct {
Base
Repo string `json:"repo" table:"repo"`
Login string `json:"login" table:"login"`
Commits *int `json:"commits,omitempty" table:"commits"`
Additions *int `json:"additions,omitempty" table:"+"`
// Hidden by the tag-grammar collision, as everywhere else.
Deletions *int `json:"deletions,omitempty" table:"-"`
// FirstWeek and LastWeek are derived by trimming the leading and trailing
// zero weeks, which turns a six-hundred-element array into two dates a
// table can show.
FirstWeek *time.Time `json:"first_week,omitempty" table:"first,time"`
LastWeek *time.Time `json:"last_week,omitempty" table:"last,time"`
Weeks []ContributorWeek `json:"weeks,omitempty" table:"-"`
AvatarURL string `json:"avatar_url,omitempty" table:"-"`
DatabaseID *int `json:"database_id,omitempty" table:"-"`
}
Contributor is one person's contribution statistics for a repository. Weeks arrives with the response but is dropped unless asked for, because the route sends every week since the repository began for every contributor and that is megabytes of mostly zeroes. It is never a table column either way, because a hundred weeks is not a column.
type ContributorOptions ¶
type ContributorOptions struct {
// Weeks keeps the per-week breakdown. It is off by default because the
// route sends every week since the repository began for every contributor,
// which on an old project is a few hundred entries each and megabytes of
// mostly zeroes for an answer whose question was "who wrote this".
Weeks bool
Limit int
}
ContributorOptions is what to do with the week array.
type ContributorWeek ¶
type ContributorWeek struct {
Week time.Time `json:"week" table:"week,time"`
Additions int `json:"additions" table:"+"`
Deletions int `json:"deletions" table:"-"`
Commits int `json:"commits" table:"commits"`
}
ContributorWeek is one week of one contributor's statistics.
type CrawlOptions ¶
type CrawlOptions struct {
Depth int
Follow []string
Kinds []string
MinTrust string
Limit int
NodesOnly bool
EdgesOnly bool
}
CrawlOptions bounds a walk. The budgets are the point of the struct. A tool that can accidentally send a million requests at somebody else's servers should be hard to point that way by accident.
type CrawlPlan ¶
type CrawlPlan struct {
Base
Seed string `json:"seed" table:"seed"`
Depth int `json:"depth" table:"depth"`
Nodes int `json:"nodes" table:"nodes"`
Note string `json:"note" table:"note"`
}
CrawlPlan is what --dry-run answers with. It is a record rather than a line on stderr so the answer goes through the same renderer, formats, and pipes as every other command, and so a script can size a walk without reading prose.
type CrawlSink ¶
CrawlSink receives what the walk finds. Emission is streaming: a crawl of a large organization must never need the whole graph in memory, and a crawl that is interrupted has already emitted everything it found.
type Dependency ¶
type Dependency struct {
Base
Repo string `json:"repo" table:"repo"`
Package string `json:"package" table:"package"`
SourceRepo string `json:"source_repo,omitempty" table:"source"`
Version string `json:"version,omitempty" table:"version"`
Relationship string `json:"relationship,omitempty" table:"rel"`
Ecosystem string `json:"ecosystem,omitempty" table:"ecosystem"`
Manifest string `json:"manifest,omitempty" table:"manifest"`
License string `json:"license,omitempty" table:"-"`
}
Dependency is one row of /network/dependencies: a package this repository declares in one of its manifests.
The identity is the repository the package resolves to, because that is the only thing on the row with an address on github.com. A package GitHub cannot resolve to a repository has no Kind and no ID, and its name is still on the record, because a dependency list with the unresolvable rows silently dropped is a lie about what the manifest contains.
type Dependent ¶
type Dependent struct {
Base
Repo string `json:"repo" table:"repo"`
Dependent string `json:"dependent" table:"dependent"`
Owner string `json:"owner" table:"-"`
Stars *int `json:"stars,omitempty" table:"stars"`
Forks *int `json:"forks,omitempty" table:"forks"`
AvatarURL string `json:"avatar_url,omitempty" table:"-"`
}
Dependent is one row of /network/dependents: a repository that depends on this one. The two counts are on the row, so a caller sorting the dependents of a popular library by stars does not need a fetch per row.
type Discussion ¶
type Discussion struct {
Thread
Category string `json:"category,omitempty" table:"category"`
IsAnswered bool `json:"is_answered" table:"answered"`
AnswerChosenAt *time.Time `json:"answer_chosen_at,omitempty" table:"-"`
AnswerAuthor *Actor `json:"answer_author,omitempty" table:"-"`
Upvotes *int `json:"upvotes,omitempty" table:"upvotes"`
}
Discussion adds the answer, which is the thing discussions have that issues do not.
type Domain ¶
type Domain struct{}
Domain is the kit driver for github.com. A blank import of this package enables it in any multi-domain host, the way a database driver registers itself, and the same Domain builds the single github binary.
func (Domain) Classify ¶
Classify satisfies kit.Resolver, so a URI typed at a multi-domain host and one typed at github are read by the same parser.
func (Domain) Info ¶
func (Domain) Info() kit.DomainInfo
Info names the domain and every hostname that means it. The extra hosts are not decoration: a pasted raw.githubusercontent.com or gist.github.com link is a github address and has to resolve here rather than fall through as an unknown site.
type Edge ¶
type Edge struct {
Subject string `json:"subject" table:"subject"`
Predicate string `json:"predicate" table:"predicate"`
Object string `json:"object" table:"object"`
Source string `json:"source" table:"source"`
Weight *int `json:"weight,omitempty" table:"weight"`
At *time.Time `json:"at,omitempty" table:"-"`
}
Edge is one directed, typed relation between two entities.
There is no inverse flag. Every predicate has exactly one direction, and where the inverse is what you want, the edge is emitted with the other node as its subject rather than with a flag saying to read it backwards.
func FilterTrust ¶
FilterTrust drops the edges below a floor, in place.
type Event ¶
type Event struct {
Base
Actor Actor `json:"actor" table:"actor"`
Type string `json:"type" table:"type"`
Repo string `json:"repo,omitempty" table:"repo"`
Title string `json:"title,omitempty" table:"title,truncate"`
BodyHTML string `json:"body_html,omitempty" table:"-"`
Target string `json:"target,omitempty" table:"-"`
At *time.Time `json:"at,omitempty" table:"at,time"`
}
Event is one entry of an activity feed. Type is derived from the entry id, which encodes the event class, rather than from the title text, which is prose and is localised.
type Fact ¶
type Fact struct {
Subject string `json:"subject" table:"subject"`
Predicate string `json:"predicate" table:"predicate"`
Value string `json:"value" table:"value,truncate"`
Datatype string `json:"datatype,omitempty" table:"-"`
}
Fact is a literal statement about a node: a star count, a description, a timestamp.
It is a separate type from Edge on purpose. Edge.Object is a URI and every consumer of the graph is entitled to treat it as one, so putting "12000" in that field to carry a star count would break each of them for the sake of saving a struct. RDF emits both; `github edges` emits only edges, which is why its output reads as relations rather than as a flattened record.
type File ¶
type File struct {
Base
Repo string `json:"repo" table:"-"`
Ref string `json:"ref" table:"-"`
Path string `json:"path" table:"path"`
Size *int64 `json:"size,omitempty" table:"size"`
// SizeDisplay is what the page shows, "13.3 KB". The page has no byte
// count anywhere, so an exact Size costs a request to raw and is filled
// only when the bytes were fetched anyway.
SizeDisplay string `json:"size_display,omitempty" table:"-"`
Lines *int `json:"lines,omitempty" table:"lines"`
Language string `json:"language,omitempty" table:"language"`
IsBinary bool `json:"is_binary" table:"-"`
IsLFS bool `json:"is_lfs" table:"-"`
IsGenerated bool `json:"is_generated" table:"-"`
IsTruncated bool `json:"is_truncated" table:"-"`
RawURL string `json:"raw_url" table:"-"`
Content string `json:"content,omitempty" table:"-"`
RawLines []string `json:"raw_lines,omitempty" table:"-"`
RichText string `json:"rich_text,omitempty" table:"-"`
TOC []Heading `json:"toc,omitempty" table:"-"`
Symbols []Symbol `json:"symbols,omitempty" table:"-"`
// SymbolsStatus is ok, timed_out, not_analyzed, or unavailable. An empty
// symbol list with not_analyzed means the language is unsupported, which is
// a different fact from a file that genuinely has no symbols, and
// unavailable means GitHub's analyser had not finished when we asked. The
// caller should not have to guess which one it got.
SymbolsStatus string `json:"symbols_status,omitempty" table:"-"`
}
File is a blob. The interesting part is Symbols: GitHub runs a symbol extractor over every blob it renders and ships the result in the route payload, and there is no unauthenticated REST equivalent anywhere.
type FileChange ¶
type FileChange struct {
Path string `json:"path" table:"path"`
PrevPath string `json:"prev_path,omitempty" table:"-"`
Status string `json:"status" table:"status"`
Additions *int `json:"additions,omitempty" table:"+"`
// Hidden by the same tag-grammar collision as PullRequest.Deletions.
Deletions *int `json:"deletions,omitempty" table:"-"`
IsBinary bool `json:"is_binary" table:"-"`
}
FileChange is one file in a commit or a diff.
type Gist ¶
type Gist struct {
Base
Owner string `json:"owner,omitempty" table:"owner"`
Description string `json:"description,omitempty" table:"description,truncate"`
IsPublic bool `json:"is_public" table:"public"`
FileCount *int `json:"file_count,omitempty" table:"files"`
Forks *int `json:"forks,omitempty" table:"forks"`
Stars *int `json:"stars,omitempty" table:"stars"`
Revisions *int `json:"revisions,omitempty" table:"-"`
Files []GistFile `json:"files,omitempty" table:"-"`
CreatedAt *time.Time `json:"created_at,omitempty" table:"created,time"`
UpdatedAt *time.Time `json:"updated_at,omitempty" table:"-"`
}
Gist is a gist and its files.
type GistFile ¶
type GistFile struct {
Name string `json:"name" table:"name"`
Language string `json:"language,omitempty" table:"language"`
Size *int64 `json:"size,omitempty" table:"size"`
RawURL string `json:"raw_url" table:"-"`
Content string `json:"content,omitempty" table:"-"`
}
GistFile is one file in a gist.
type GitRef ¶
type GitRef struct {
Base
Repo string `json:"repo" table:"-"`
Name string `json:"name" table:"name"`
Type string `json:"type" table:"type"`
SHA string `json:"sha,omitempty" table:"sha"`
// PeeledSHA is set for annotated tags, from the ^{} entry in the git
// protocol advertisement.
PeeledSHA string `json:"peeled_sha,omitempty" table:"-"`
IsDefault bool `json:"is_default" table:"default"`
Protected bool `json:"protected" table:"protected"`
Author *Actor `json:"author,omitempty" table:"author"`
AuthoredAt *time.Time `json:"authored_at,omitempty" table:"authored,time"`
}
GitRef is a branch or a tag. It is not called Ref because Ident already owns the word "reference" in this package, and a git ref and a parsed URI are very different things to confuse in a stack trace.
Three surfaces carry refs and each is incomplete differently: the branches page has authors and dates but a truncated list, the refs XHR has every name and nothing else, and the git protocol has every name with its SHA. The commands pick per question, which is why `github refs --names-only` is 6 KB where `github refs` is 588 KB.
type Graph ¶
type Graph struct {
Nodes []Node `json:"nodes"`
Edges []Edge `json:"edges"`
Facts []Fact `json:"facts,omitempty"`
}
Graph is a set of nodes, edges, and facts held in memory. The streaming commands never build one; `github graph` for a single entity, `github rdf` for the buffered serialisations, and the tests all want the whole thing in hand.
type Heading ¶
type Heading struct {
Level int `json:"level" table:"level"`
Text string `json:"text" table:"text"`
Anchor string `json:"anchor" table:"anchor"`
}
Heading is one entry of a rendered markdown table of contents.
type Ident ¶
type Ident struct {
Kind string `json:"kind" table:"kind"`
ID string `json:"id" table:"id"`
Anchor string `json:"anchor,omitempty" table:"anchor"`
URI string `json:"uri" table:"uri"`
URL string `json:"url" table:"url,url"`
}
Ident is a parsed reference: what kind of thing, its canonical id, and the fragment the URL carried. The fragment never changes the kind. A link to #issuecomment-66046293 is still a link to the issue, and a link to #L10-L20 is still a link to the file, so the anchor is recorded and set aside.
type Issue ¶
type Issue struct {
Thread
IssueType string `json:"issue_type,omitempty" table:"-"`
SubIssueTotal *int `json:"sub_issue_total,omitempty" table:"-"`
SubIssueDone *int `json:"sub_issue_done,omitempty" table:"-"`
DuplicateOf string `json:"duplicate_of,omitempty" table:"-"`
LinkedPRs []string `json:"linked_prs,omitempty" table:"-"`
ClosedByPRs []string `json:"closed_by_prs,omitempty" table:"-"`
ProjectItems []string `json:"project_items,omitempty" table:"-"`
}
Issue adds the tracking relationships GitHub keeps between issues and the work that closes them.
type Label ¶
type Label struct {
Name string `json:"name" table:"name"`
Color string `json:"color,omitempty" table:"color"`
Description string `json:"description,omitempty" table:"description,truncate"`
URL string `json:"url,omitempty" table:"url,url"`
NodeID string `json:"node_id,omitempty" table:"-"`
}
Label is a thread label.
type LanguageShare ¶
type LanguageShare struct {
}
LanguageShare is one language of one repository. The repository record carries the same numbers as a map, which is the right shape to keep and the wrong shape to print, so this is the row form of it.
type Milestone ¶
type Milestone struct {
Title string `json:"title" table:"title"`
Number *int `json:"number,omitempty" table:"number"`
Closed bool `json:"closed" table:"closed"`
DueOn *time.Time `json:"due_on,omitempty" table:"due,time"`
ClosedAt *time.Time `json:"closed_at,omitempty" table:"-"`
Progress *float64 `json:"progress,omitempty" table:"progress"`
URL string `json:"url,omitempty" table:"url,url"`
}
Milestone is a thread milestone.
type Node ¶
type Node struct {
URI string `json:"uri" table:"uri"`
Kind string `json:"kind" table:"kind"`
ID string `json:"id" table:"id"`
Label string `json:"label,omitempty" table:"label,truncate"`
URL string `json:"url,omitempty" table:"url,url"`
}
Node is one entity. It is deliberately thin: the label and the two addresses and nothing else, because the full record is one `github get` away by URI and duplicating it here would make a crawl of ten thousand nodes unprintable.
type Org ¶
type Org struct {
Account
VerifiedDomains []string `json:"verified_domains,omitempty" table:"-"`
MemberCount *int `json:"member_count,omitempty" table:"members"`
// Members is the avatar strip on the front page, which is a sample and not
// the roster. It never sets MemberCount for that reason: `github members`
// walks /orgs/{login}/people for the real list.
Members []string `json:"members,omitempty" table:"-"`
TopLanguages map[string]int `json:"top_languages,omitempty" table:"-"`
TopTopics []string `json:"top_topics,omitempty" table:"-"`
IsEnterprise bool `json:"is_enterprise" table:"-"`
}
Org is an Account plus the five things only an organization page has. TopLanguages and TopTopics are deferred fragments and arrive only with --deep.
type Package ¶
type Package struct {
Base
Repo string `json:"repo,omitempty" table:"repo"`
Name string `json:"name" table:"name"`
Type string `json:"type" table:"type"`
Summary string `json:"summary,omitempty" table:"summary,truncate"`
Downloads *int `json:"downloads,omitempty" table:"downloads"`
Topics []string `json:"topics,omitempty" table:"-"`
Source string `json:"source,omitempty" table:"-"`
UpdatedAt *time.Time `json:"updated_at,omitempty" table:"updated,time"`
}
Package is a published package. Search is the only source, which means the record is complete the moment it is read.
type PullRequest ¶
type PullRequest struct {
Thread
BaseRef string `json:"base_ref,omitempty" table:"base"`
HeadRef string `json:"head_ref,omitempty" table:"head"`
BaseOID string `json:"base_oid,omitempty" table:"-"`
HeadOID string `json:"head_oid,omitempty" table:"-"`
Merged bool `json:"merged" table:"-"`
MergedAt *time.Time `json:"merged_at,omitempty" table:"merged,time"`
MergedBy *Actor `json:"merged_by,omitempty" table:"-"`
Mergeable string `json:"mergeable,omitempty" table:"-"`
IsDraft bool `json:"is_draft" table:"-"`
Additions *int `json:"additions,omitempty" table:"+"`
// The render tag grammar uses "-" to mean "skip this column", and "-" is
// also the natural header for deletions. The grammar wins: deletions is
// hidden by default and shown with --fields deletions. This is deliberate,
// please do not "fix" it.
Deletions *int `json:"deletions,omitempty" table:"-"`
ChangedFiles *int `json:"changed_files,omitempty" table:"files"`
CommitCount *int `json:"commit_count,omitempty" table:"-"`
ReviewDecision string `json:"review_decision,omitempty" table:"review"`
ReviewRequests []Actor `json:"review_requests,omitempty" table:"-"`
ClosesIssues []string `json:"closes_issues,omitempty" table:"-"`
}
PullRequest adds the diff and the merge state.
type RDFOptions ¶
type RDFOptions struct {
Format string
// Graph is the fourth position for N-Quads. Putting the source URL there
// means the provenance survives into the RDF and a quad store can answer
// which page told us this.
Graph string
}
RDFOptions controls a serialisation.
type RDFWriter ¶
type RDFWriter struct {
// contains filtered or unexported fields
}
RDFWriter is the streaming form. A crawl hands it nodes, edges, and facts as it finds them and it writes lines, so `github export --depth 3 --format nt` over a large organization never holds the graph in memory. The buffered formats are handled by collecting into a Graph and calling WriteRDF, and this type reports which is which through Streams.
func NewRDFWriter ¶
func NewRDFWriter(w io.Writer, o RDFOptions) *RDFWriter
NewRDFWriter returns a streaming writer for nt or nq, and nil for the formats that cannot stream.
type Reaction ¶
type Reaction struct {
Content string `json:"content" table:"content"`
Count int `json:"count" table:"count"`
}
Reaction is one emoji group. Content is the GraphQL enum: THUMBS_UP, THUMBS_DOWN, LAUGH, HOORAY, CONFUSED, HEART, ROCKET, EYES. All eight always arrive, most with a zero count, and the decoder drops the zeroes so an unreacted thread has an empty list rather than eight noisy nothings.
type RefOptions ¶
type RefOptions struct {
// Complete reads the git advertisement instead of the branches page: every
// ref in one request, with SHAs, and no cap. It costs the author and date
// the page carries, because the protocol does not have them.
Complete bool
// Pulls includes refs/pull/*, which github.com advertises for every pull
// request ever opened. On a busy repository that is most of the response.
Pulls bool
Limit int
}
RefOptions controls a ref listing.
type Release ¶
type Release struct {
Base
Repo string `json:"repo" table:"-"`
Tag string `json:"tag" table:"tag"`
Title string `json:"title,omitempty" table:"title,truncate"`
Body string `json:"body,omitempty" table:"-"`
BodyHTML string `json:"body_html,omitempty" table:"-"`
Author *Actor `json:"author,omitempty" table:"author"`
PublishedAt *time.Time `json:"published_at,omitempty" table:"published,time"`
UpdatedAt *time.Time `json:"updated_at,omitempty" table:"-"`
IsPrerelease bool `json:"is_prerelease" table:"pre"`
IsLatest bool `json:"is_latest" table:"latest"`
IsDraft bool `json:"is_draft" table:"-"`
CommitSHA string `json:"commit_sha,omitempty" table:"-"`
Assets []Asset `json:"assets,omitempty" table:"-"`
TarballURL string `json:"tarball_url,omitempty" table:"-"`
ZipballURL string `json:"zipball_url,omitempty" table:"-"`
// RepoDatabaseID comes free from the Atom <id>, which is
// tag:github.com,2008:Repository/11180687/v0.164.0. That is how a release
// read from a feed joins to a repository record without a second fetch.
RepoDatabaseID *int `json:"repo_database_id,omitempty" table:"-"`
}
Release is one published release. Assets and download counts exist only on the per-release HTML page, so the feed-driven listing leaves Assets nil and `--assets` opts into one request per release.
type ReleaseOptions ¶
type ReleaseOptions struct {
// Assets fetches the lazy asset fragment for each release, one extra
// request each. Without it a release record has no downloads.
Assets bool
// Body keeps the rendered release notes, which are most of the bytes on a
// project that writes a changelog.
Body bool
Limit int
}
ReleaseOptions controls a release listing.
type Repo ¶
type Repo struct {
Base
Owner string `json:"owner" table:"owner"`
Name string `json:"name" table:"name"`
Description string `json:"description,omitempty" table:"description,truncate"`
DescriptionHighlight string `json:"description_highlight,omitempty" table:"-"`
Homepage string `json:"homepage,omitempty" table:"-"`
Topics []string `json:"topics,omitempty" table:"topics"`
DatabaseID *int `json:"database_id,omitempty" table:"-"`
NodeID string `json:"node_id,omitempty" table:"-"`
DefaultBranch string `json:"default_branch,omitempty" table:"branch"`
HeadSHA string `json:"head_sha,omitempty" table:"-"`
Language string `json:"language,omitempty" table:"language"`
LanguageColor string `json:"language_color,omitempty" table:"-"`
Languages map[string]int64 `json:"languages,omitempty" table:"-"`
Stars *int `json:"stars,omitempty" table:"stars"`
StarsDisplay string `json:"stars_display,omitempty" table:"-"`
Forks *int `json:"forks,omitempty" table:"forks"`
Watchers *int `json:"watchers,omitempty" table:"watchers"`
OpenIssues *int `json:"open_issues,omitempty" table:"issues"`
GoodFirstIssues *int `json:"good_first_issues,omitempty" table:"-"`
HelpWantedIssues *int `json:"help_wanted_issues,omitempty" table:"-"`
CommitCount *int `json:"commit_count,omitempty" table:"-"`
CommitCountDisplay string `json:"commit_count_display,omitempty" table:"-"`
ReleaseCount *int `json:"release_count,omitempty" table:"-"`
TagCount *int `json:"tag_count,omitempty" table:"-"`
FileCount *int `json:"file_count,omitempty" table:"-"`
DependentCount *int `json:"dependent_count,omitempty" table:"-"`
ContributorCount *int `json:"contributor_count,omitempty" table:"-"`
// License comes from one sidebar anchor and from nowhere else on any
// keyless surface. See page.LicenseLink.
License string `json:"license,omitempty" table:"license"`
IsFork bool `json:"is_fork" table:"-"`
ForkOf string `json:"fork_of,omitempty" table:"-"`
IsArchived bool `json:"is_archived" table:"-"`
IsMirror bool `json:"is_mirror" table:"-"`
IsTemplate bool `json:"is_template" table:"-"`
IsEmpty bool `json:"is_empty" table:"-"`
IsPrivate bool `json:"is_private" table:"-"`
IsOrgOwned bool `json:"is_org_owned" table:"-"`
Visibility string `json:"visibility,omitempty" table:"-"`
Sponsorable bool `json:"sponsorable" table:"-"`
HasFunding bool `json:"has_funding" table:"-"`
HasCitation bool `json:"has_citation" table:"-"`
HasDiscussions bool `json:"has_discussions" table:"-"`
HasWiki bool `json:"has_wiki" table:"-"`
HasPages bool `json:"has_pages" table:"-"`
CreatedAt *time.Time `json:"created_at,omitempty" table:"created,time"`
PushedAt *time.Time `json:"pushed_at,omitempty" table:"pushed,time"`
UpdatedAt *time.Time `json:"updated_at,omitempty" table:"updated,time"`
OwnerAvatarURL string `json:"owner_avatar_url,omitempty" table:"-"`
SocialImageURL string `json:"social_image_url,omitempty" table:"-"`
ReadmePath string `json:"readme_path,omitempty" table:"-"`
ReadmeHTML string `json:"readme_html,omitempty" table:"-"`
ReadmeText string `json:"readme_text,omitempty" table:"-"`
Tree []TreeEntry `json:"tree,omitempty" table:"-"`
StargazersPath string `json:"stargazers_path,omitempty" table:"-"`
ForkNetworkPath string `json:"fork_network_path,omitempty" table:"-"`
ActivityPath string `json:"activity_path,omitempty" table:"-"`
}
Repo is the centre of the model. A read from a page fills most of it, a read from a search result fills a thinner but honest subset, and Sources says which happened.
type RepoOptions ¶
type RepoOptions struct {
// Deep runs the fragment fetches: the language histogram and the dependent
// count. Four to six requests instead of one.
Deep bool
// Readme keeps the rendered README, which is most of the response body on a
// well-documented repository.
Readme bool
}
RepoOptions controls how much a repository read costs.
type RepoStats ¶
type RepoStats struct {
Base
Repo string `json:"repo" table:"repo"`
Stars *int `json:"stars,omitempty" table:"stars"`
Forks *int `json:"forks,omitempty" table:"forks"`
Watchers *int `json:"watchers,omitempty" table:"watching"`
OpenIssues *int `json:"open_issues,omitempty" table:"issues"`
Commits *int `json:"commits,omitempty" table:"commits"`
Releases *int `json:"releases,omitempty" table:"releases"`
Tags *int `json:"tags,omitempty" table:"tags"`
Contributors *int `json:"contributors,omitempty" table:"people"`
Dependents *int `json:"dependents,omitempty" table:"used_by"`
PushedAt *time.Time `json:"pushed_at,omitempty" table:"pushed,time"`
}
RepoStats is the counts and nothing else.
Every field is already on Repo. The reason to have it separately is that a record with eight numbers in it is something you can store once a day and diff; a record with a readme in it is not.
type Response ¶
type Response struct {
Body []byte
Status int
Header http.Header
URL string
FinalURL string
Surface Surface
}
Response is one completed exchange. FinalURL differs from URL when GitHub redirected, which is how a renamed repository is detected without a second request.
type RouteInfo ¶
type RouteInfo struct {
Route string `json:"route" table:"route"`
Primary string `json:"primary" table:"primary"`
Fallback string `json:"fallback,omitempty" table:"fallback"`
Note string `json:"note,omitempty" table:"note,truncate"`
}
RouteInfo is one row: what the route is, which surface answers it best, and what to fall back to when that surface declines.
type Surface ¶
type Surface int
Surface names one of the eight ways this tool reads github.com. It decides the request headers and how a non-200 is read, which is why it travels with every request instead of being guessed from the URL.
const ( // SurfaceHTML is a plain page fetch. Every route serves one. SurfaceHTML Surface = iota // SurfaceRouteJSON asks a React route for its props with Accept: json. SurfaceRouteJSON // SurfaceXHR sets X-Requested-With, which unlocks the fragments the front // end fetches for itself: refs, contributor statistics, hovercards. SurfaceXHR // SurfaceSearch is /search?q=&type=, which answers JSON for the asking. SurfaceSearch // SurfaceFeed is an .atom endpoint. SurfaceFeed // SurfaceRaw is raw.githubusercontent.com and codeload: bytes, no page. SurfaceRaw // SurfaceGit is the git smart protocol at /{owner}/{repo}.git/info/refs. SurfaceGit )
type Symbol ¶
type Symbol struct {
Name string `json:"name" table:"name"`
Kind string `json:"kind" table:"kind"`
FullyQualifiedName string `json:"fqn,omitempty" table:"-"`
IdentStart int `json:"ident_start" table:"-"`
IdentEnd int `json:"ident_end" table:"-"`
ExtentStart int `json:"extent_start" table:"-"`
ExtentEnd int `json:"extent_end" table:"-"`
}
Symbol is one extracted definition, with byte offsets into the blob.
type Thread ¶
type Thread struct {
Base
Repo string `json:"repo" table:"repo"`
Number int `json:"number" table:"number"`
Title string `json:"title" table:"title,truncate"`
TitleHighlight string `json:"title_highlight,omitempty" table:"-"`
TitleHTML string `json:"title_html,omitempty" table:"-"`
State string `json:"state" table:"state"`
StateReason string `json:"state_reason,omitempty" table:"-"`
Body string `json:"body,omitempty" table:"-"`
BodyHTML string `json:"body_html,omitempty" table:"-"`
Author Actor `json:"author" table:"author"`
Labels []Label `json:"labels,omitempty" table:"labels"`
Milestone *Milestone `json:"milestone,omitempty" table:"-"`
Assignees []Actor `json:"assignees,omitempty" table:"-"`
Reactions []Reaction `json:"reactions,omitempty" table:"-"`
CommentCount *int `json:"comment_count,omitempty" table:"comments"`
Locked bool `json:"locked" table:"-"`
IsPinned bool `json:"is_pinned" table:"-"`
CreatedAt *time.Time `json:"created_at,omitempty" table:"created,time"`
UpdatedAt *time.Time `json:"updated_at,omitempty" table:"updated,time"`
ClosedAt *time.Time `json:"closed_at,omitempty" table:"-"`
AuthorAssociation string `json:"author_association,omitempty" table:"-"`
NodeID string `json:"node_id,omitempty" table:"-"`
DatabaseID *int `json:"database_id,omitempty" table:"-"`
}
Thread is what issues, pull requests, and discussions have in common, which is most of it: their pages share a Relay payload shape.
type ThreadRef ¶
type ThreadRef struct {
DatabaseID *int `json:"database_id,omitempty" table:"id"`
Title string `json:"title,omitempty" table:"title,truncate"`
State string `json:"state,omitempty" table:"state"`
IsPullRequest bool `json:"is_pull_request" table:"-"`
Merged bool `json:"merged" table:"-"`
URL string `json:"url,omitempty" table:"url"`
}
ThreadRef is a pointer to an issue or a pull request from somewhere else. It is not a Thread: it carries only what the referring surface knew, and the caller resolves it with `github get` when it wants the rest.
type TimelineItem ¶
type TimelineItem struct {
Base
Thread string `json:"thread" table:"-"`
Type string `json:"type" table:"type"`
Cursor string `json:"cursor,omitempty" table:"-"`
Actor *Actor `json:"actor,omitempty" table:"actor"`
CreatedAt *time.Time `json:"created_at,omitempty" table:"created,time"`
Body string `json:"body,omitempty" table:"body,truncate"`
BodyHTML string `json:"body_html,omitempty" table:"-"`
Label *Label `json:"label,omitempty" table:"-"`
Milestone *Milestone `json:"milestone,omitempty" table:"-"`
Assignee *Actor `json:"assignee,omitempty" table:"-"`
FromTitle string `json:"from_title,omitempty" table:"-"`
ToTitle string `json:"to_title,omitempty" table:"-"`
Commit string `json:"commit,omitempty" table:"-"`
Source string `json:"source,omitempty" table:"-"`
Reactions []Reaction `json:"reactions,omitempty" table:"-"`
Minimized bool `json:"minimized" table:"-"`
MinimizedReason string `json:"minimized_reason,omitempty" table:"-"`
CreatedViaEmail bool `json:"created_via_email" table:"-"`
LastEditedAt *time.Time `json:"last_edited_at,omitempty" table:"-"`
}
TimelineItem is one event on a thread. Type is the GraphQL __typename, lower-snake-cased.
An unrecognised typename does not get dropped: Type is set, the common fields are filled, the whole node goes into Extra, and the fixture suite fails and names it. That is the entire strategy for union drift.
type Topic ¶
type Topic struct {
Base
Name string `json:"name" table:"name"`
DisplayName string `json:"display_name,omitempty" table:"display"`
ShortDescription string `json:"short_description,omitempty" table:"description,truncate"`
Description string `json:"description,omitempty" table:"-"`
DescriptionHTML string `json:"description_html,omitempty" table:"-"`
LogoURL string `json:"logo_url,omitempty" table:"-"`
WikipediaURL string `json:"wikipedia_url,omitempty" table:"-"`
GitHubURL string `json:"github_url,omitempty" table:"-"`
CreatedBy string `json:"created_by,omitempty" table:"-"`
Released string `json:"released,omitempty" table:"released"`
Aliases []string `json:"aliases,omitempty" table:"-"`
Related []string `json:"related,omitempty" table:"-"`
StargazerCount *int `json:"stargazer_count,omitempty" table:"stars"`
AppliedCount *int `json:"applied_count,omitempty" table:"repos"`
Featured bool `json:"featured" table:"-"`
Curated bool `json:"curated" table:"-"`
}
Topic is a curated or uncurated topic. The search result carries most of it; the long description, the logo, the creator, the release year, the Wikipedia link, and the aliases need the topic page.
type TreeEntry ¶
type TreeEntry struct {
Base
Repo string `json:"repo" table:"-"`
Ref string `json:"ref" table:"-"`
Name string `json:"name" table:"name"`
Path string `json:"path" table:"path"`
// Type is contentType verbatim: file, directory, symlink_file,
// symlink_directory, submodule.
Type string `json:"type" table:"type"`
Size *int64 `json:"size,omitempty" table:"size"`
SHA string `json:"sha,omitempty" table:"-"`
}
TreeEntry is one row of a directory listing. Size and SHA are absent from the tree route and cost one request each, which is what `--sizes` opts into.
type TreeOptions ¶
type TreeOptions struct {
// Ref is a branch, a tag, or a SHA. Empty means the default branch, which
// GitHub resolves itself when the ref in the URL is HEAD, so an empty ref
// costs no extra request.
Ref string
// Recursive walks subdirectories breadth-first. There is no ?recursive=1 on
// this route, so this is one request per directory. For a whole large tree,
// Archive is one request instead of hundreds.
Recursive bool
// Sizes fills Size on file entries, at one HEAD request each.
Sizes bool
// Limit stops the walk after this many entries. Zero means no limit.
Limit int
}
TreeOptions controls a directory listing.
type Trending ¶
type Trending struct {
Repo
StarsInPeriod *int `json:"stars_in_period,omitempty" table:"period_stars"`
Period string `json:"period" table:"period"`
BuiltBy []Actor `json:"built_by,omitempty" table:"-"`
Rank int `json:"rank" table:"rank"`
}
Trending embeds Repo because a trending entry is a repository with three extra facts. Embedding is what makes `github trending -o url | xargs -n1 github get` work with no special case anywhere.
type TrendingOptions ¶
type TrendingOptions struct {
// Since is daily, weekly, or monthly. Empty means daily, which is what the
// page defaults to.
Since string
// Language filters by the language slug in the URL, not by a query.
Language string
// SpokenLanguage is the natural-language filter, a two-letter code.
SpokenLanguage string
Limit int
}
TrendingOptions are the three knobs the trending page has.
type WikiPage ¶
type WikiPage struct {
Base
Repo string `json:"repo" table:"repo"`
Title string `json:"title" table:"title"`
Path string `json:"path" table:"path"`
Format string `json:"format,omitempty" table:"-"`
Body string `json:"body,omitempty" table:"-"`
BodyHTML string `json:"body_html,omitempty" table:"-"`
UpdatedAt *time.Time `json:"updated_at,omitempty" table:"updated,time"`
Author *Actor `json:"author,omitempty" table:"author"`
}
WikiPage is one page of a repository wiki.