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

github

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package github fetches repository and Dependabot pull request state and performs the few writes deps-merger makes (merges and comments). It reuses the gh CLI's authentication.

Index

Constants

View Source
const MaxDiffBytes = 200_000

MaxDiffBytes caps the diff read for a review: lockfile churn can run to megabytes and says little.

View Source
const MaxRateLimitWait = 2 * time.Minute

MaxRateLimitWait is the longest a read (through Backoff) or a write waits for a rate limit to lift; a longer one fails the call rather than stall the run.

View Source
const RequestTimeout = time.Minute

RequestTimeout bounds each request to GitHub, reading the response included. Without it one request that hangs would hold the lock that serialises writes, and with it every repository, for good. A merge that times out may still have gone through: the error is Transient, so it is treated like a 504.

Variables

This section is empty.

Functions

func Again

func Again[T any](ctx context.Context, pause time.Duration, sleep func(context.Context, time.Duration) error, read func() (T, error)) (T, error)

Again calls read, and once more after the pause Backoff gives when it fails in a way that may pass, so one hiccup does not fail a scheduled run. sleep waits for a duration unless ctx ends first; nil uses a timer.

func Backoff

func Backoff(err error, pause time.Duration) (time.Duration, bool)

Backoff says how long to pause before making a failed call again, and whether to make it again at all: pause after a transient failure, and as long as GitHub asks after a rate limit, unless that is over MaxRateLimitWait.

func DependabotRepos

func DependabotRepos(ctx context.Context, owners []string) ([]string, error)

DependabotRepos lists, as owner/name, the repositories of owners, or those the gh login owns, collaborates on or can reach as an organisation member when owners is empty, that hold a Dependabot config and that it can write to, leaving out archived repositories and forks. The list is sorted, case aside, each repository once.

func NotWaiting

func NotWaiting(err error) error

NotWaiting adds to a rate limit error that it was not waited out, and for how long it holds, when GitHub says. Any other error is returned as it is.

func RateLimited

func RateLimited(err error) (time.Duration, bool)

RateLimited reports whether GitHub refused a call for exceeding a rate limit and, if so, how long it asked to wait before the next one.

func Refused

func Refused(err error) bool

Refused reports whether GitHub answered a call with an error status that says it was not carried out: any but the 502, 503 and 504 Transient counts, after which a write may have gone through all the same.

func Retryable

func Retryable(err error) bool

Retryable reports whether a failed merge may succeed once GitHub catches up: 405 (not mergeable yet) and 409 (head moved) are often transient right after another merge into the base branch.

func Sleep

func Sleep(ctx context.Context, d time.Duration) error

Sleep waits for d, or until ctx ends.

func Transient

func Transient(err error) bool

Transient reports whether a failed call may succeed if made again: GitHub answers a heavy query it could not finish in time with a 502 or 504, or with a GraphQL error saying something went wrong, and a connection can time out, or be closed or reset, on the way. For a merge or comment, it means the write may have gone through all the same. A call cut short by the run being cancelled can fail in these ways too: callers rule that out first.

Types

type Author

type Author struct {
	Login string `json:"login,omitempty"`
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

type Check

type Check struct {
	Name  string     `json:"name"`
	State CheckState `json:"state"`
	// Suite identifies the check suite (one workflow run) a check run
	// belongs to; a newer suite of the same app, workflow and event with
	// the same check name supersedes it, as when concurrency cancels a run
	// in favour of a later one. Zero for status contexts.
	Suite int64 `json:"-"`
	// App and Workflow identify what produced a check run: the GitHub App
	// behind its suite and, for GitHub Actions, the workflow. Two workflows
	// can each have a job called "test"; neither says anything about the
	// other's.
	App      int64 `json:"-"`
	Workflow int64 `json:"-"`
	// Event is what triggered a GitHub Actions workflow run, such as
	// "push" or "pull_request". A workflow listening to both runs twice on
	// the same commit, and a run for one event never supersedes the other.
	Event string `json:"-"`
	// Status is true for a commit status context rather than a check run.
	// GitHub reports only the latest status per context, and a status
	// never stands in for a check run of the same name or the reverse.
	Status bool `json:"-"`
}

type CheckState

type CheckState string

CheckState is the normalised outcome of one check run or status context.

const (
	CheckPassed  CheckState = "passed"
	CheckPending CheckState = "pending"
	CheckFailed  CheckState = "failed"
	// CheckSkipped is a check run that completed without judging anything:
	// skipped, as a job whose condition was false is, or neutral. It blocks
	// nothing, but neither does it count as a check that ran.
	CheckSkipped CheckState = "skipped"
	// CheckStale is a check run GitHub gave up on after it sat unfinished
	// for 14 days: it will never pass, but says nothing about the update.
	CheckStale CheckState = "stale"
)

type Client

type Client interface {
	Viewer(ctx context.Context) (string, error)
	FetchRepo(ctx context.Context, owner, name string) (*Repo, error)
	Merge(ctx context.Context, owner, name string, number int, method, headOID string) error
	Comment(ctx context.Context, owner, name string, number int, body string) error
	PullRequestDetails(ctx context.Context, owner, name string, number int) (*PullRequestDetails, error)
	PullRequestState(ctx context.Context, owner, name string, number int) (PRState, error)
	// FileText reads the file at path on the repository's default branch.
	// It returns nil, and no error, when the repository or the file does
	// not exist or cannot be seen.
	FileText(ctx context.Context, owner, name, path string) ([]byte, error)
}

Client is the subset of GitHub operations deps-merger needs.

func New

func New() (Client, error)

New builds a client from the gh CLI's stored credentials or GH_TOKEN.

func NewWithToken

func NewWithToken(token string) (Client, error)

NewWithToken builds a github.com client that authenticates with token, such as a GitHub App installation token, and reads nothing of the gh CLI's: neither its credentials nor its config.

func WithPermission

func WithPermission(c Client, permission string) Client

WithPermission returns c with permission filled in as the viewer's permission on every repository GitHub reports none for. GitHub reports none for a GitHub App installation, whose permissions the caller knows instead. A permission GitHub does report is kept.

type Comment

type Comment struct {
	Author    string    `json:"author"`
	Body      string    `json:"body"`
	CreatedAt time.Time `json:"created_at"`
}

type Commit

type Commit struct {
	OID           string    `json:"oid"`
	Message       string    `json:"-"`
	CommittedDate time.Time `json:"committed_date"`
	// Authors is the commit author followed by every Co-authored-by
	// trailer. The first is always the author GitHub recorded, empty when
	// it recorded none; only it can carry a Login, since a trailer links no
	// account.
	Authors []Author `json:"authors"`
	// SignedByGitHub is true when the commit carries a valid signature made
	// with GitHub's own key, as Dependabot's commits do.
	SignedByGitHub bool `json:"signed_by_github"`
	// CommittedByGitHub is true when GitHub itself is the committer, as on
	// every commit Dependabot makes. An API call that names its own
	// committer gets no such commit, even when GitHub signs it.
	CommittedByGitHub bool `json:"committed_by_github"`
}

type PRState

type PRState string

PRState is whether a pull request is open, merged, or closed without being merged.

const (
	PROpen   PRState = "OPEN"
	PRMerged PRState = "MERGED"
	PRClosed PRState = "CLOSED"
)

type PullRequest

type PullRequest struct {
	Number         int       `json:"number"`
	Title          string    `json:"title"`
	URL            string    `json:"url"`
	Author         string    `json:"author"`
	IsDraft        bool      `json:"is_draft"`
	CreatedAt      time.Time `json:"created_at"`
	HeadOID        string    `json:"head_oid"`
	BaseRef        string    `json:"base_ref"`
	Mergeable      string    `json:"mergeable"`       // MERGEABLE, CONFLICTING, UNKNOWN
	MergeState     string    `json:"merge_state"`     // CLEAN, BEHIND, BLOCKED, DIRTY, ...
	ReviewDecision string    `json:"review_decision"` // APPROVED, REVIEW_REQUIRED, ...
	CommitCount    int       `json:"commit_count"`
	Commits        []Commit  `json:"commits"`
	Checks         []Check   `json:"checks"`
	// ChecksOID is the commit Checks were read from; checks for any commit
	// but HeadOID say nothing about what would be merged.
	ChecksOID string `json:"checks_oid"`
	// CheckCount is the total number of checks, which can exceed the number
	// fetched into Checks.
	CheckCount int `json:"check_count"`
	// BaseMovedAt is when the latest commit on the base branch was made, as
	// a merge into it does; zero when GitHub cannot say.
	BaseMovedAt time.Time `json:"base_moved_at"`
	// Comments are the latest comments, oldest first; CommentCount is how
	// many the PR has, which can exceed the number fetched.
	Comments     []Comment `json:"-"`
	CommentCount int       `json:"-"`
}

type PullRequestDetails

type PullRequestDetails struct {
	// Body is the PR description, where Dependabot puts release notes,
	// changelog entries and commits.
	Body string
	// Diff is the PR's unified diff, cut at MaxDiffBytes.
	Diff      string
	Truncated bool
	// DiffError says why Diff is empty, such as GitHub refusing a diff too
	// large to render.
	DiffError string
}

PullRequestDetails is what an agent review reads about a PR beyond what FetchRepo returns, fetched only for the PRs it reviews.

type RateLimitError

type RateLimitError struct {
	Err   error
	Reset time.Time
}

RateLimitError is a GraphQL query refused for the rate limit, with when the limit lifts: unlike REST, GitHub's GraphQL error does not say.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

type Repo

type Repo struct {
	FullName      string        `json:"full_name"`
	Archived      bool          `json:"archived"`
	Permission    string        `json:"permission"` // ADMIN, MAINTAIN, WRITE, TRIAGE, READ
	SquashAllowed bool          `json:"squash_allowed"`
	MergeAllowed  bool          `json:"merge_allowed"`
	RebaseAllowed bool          `json:"rebase_allowed"`
	PullRequests  []PullRequest `json:"pull_requests"`
	// OpenPRCount is how many open Dependabot PRs GitHub reports, which can
	// exceed the number fetched into PullRequests.
	OpenPRCount int `json:"open_pr_count"`
	// SearchCount is how many results the search returned, before closed
	// PRs still in its index were dropped. Only when it falls short of
	// OpenPRCount were some PRs left unread: GitHub counts those closed PRs
	// too.
	SearchCount int `json:"search_count"`
	// FetchedAt is when this state was read, the "now" that the age of
	// commits and comments is measured against.
	FetchedAt time.Time `json:"-"`
}

func (*Repo) CanWrite

func (r *Repo) CanWrite() bool

CanWrite reports whether the viewer can merge and comment as a collaborator.

type WorkflowScopeError

type WorkflowScopeError struct{ Err error }

WorkflowScopeError is a merge refused because the PR changes a file under .github/workflows and the token lacks the workflow scope GitHub asks for to write one, as gh's default login does.

func (*WorkflowScopeError) Error

func (e *WorkflowScopeError) Error() string

func (*WorkflowScopeError) Unwrap

func (e *WorkflowScopeError) Unwrap() error

Jump to

Keyboard shortcuts

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