Documentation
¶
Overview ¶
Package pullrequest holds a run's pull request: its record on disk and the GitHub calls on it.
Record is the run's one JSON file about its pull request. It lives in the run's scratch directory, so it is never tracked and goes with the pair. It holds the pull request's number, URL, and base and head branches. It also holds the head the PR-Review row last gated and pushed and the head whose gate failure was mailed. It holds the interactive session's strand and session identity and engine, and the operator's continue flag. It holds whether the review ended. The row ends it on that flag. Finalize ends it on the record it opens itself, for a parent that takes its pull request with no review. ReadRecord, WriteRecord, UpdateRecord and RemoveRecord go through internal/state, so a key a newer binary wrote survives a rewrite. An absent file reads as not found. UpdateRecord on an absent record returns ErrNoRecord and writes nothing. RemoveRecord of an absent file is no error.
A Client is bound to one repository. Open authenticates only through githubclient and calls go-github directly, never the gh command; NewWithClient binds a told client, which tests point at an httptest server. The API interface lists the calls a caller makes, so a test of the caller fakes GitHub behind it. Each call takes the caller's context, and githubclient's own per-request time bound applies as well. RequiresPullRequest reports whether a branch's active rules include a pull_request rule. GitHub refuses a direct push under such a rule. It reads `GET repos/{owner}/{repo}/rules/branches/{branch}`. SetDraft changes a pull request's draft state, since the REST API cannot. It runs the GraphQL `convertPullRequestToDraft` or `markPullRequestReadyForReview` on the node id. An error GitHub lists in the GraphQL response is a failure. Status reads the state (merged wins over closed), the head SHA and whether the pull request is a draft. It also reads GitHub's mergeable_state. It reads whether a standing review approves that head or requests changes; a reviewer's standing review is the latest of their approvals, change requests and dismissals, so a later comment changes nothing. RequiredChecks reads every page of a base branch's active rules for its required status-check contexts. It then reads each context's conclusion at a head: from the check run of that name, else from the commit status of that context, else CheckMissing; a pending commit status reads as unfinished. ReadMergeState reads a pull request's Status and the required checks of its base. It judges whether GitHub would merge it now, naming each cause when it would not; a mergeable_state GitHub has not yet computed is read again until MergeRetryWindow has passed on the told clock. A merge GitHub refuses with HTTP 405 or 409 returns a MergeRefusal. Its kind is RefusalMethod when the merge method is not allowed. Its kind is RefusalPending when the base branch was modified or mergeability is still being computed. Its kind is RefusalOther for any other refusal. Every other failure is returned wrapped, so githubclient.IsTransient still judges it.
The package files no issue: filing LoomYard bugs is selfreport's alone.
Index ¶
- Constants
- Variables
- func RemoveRecord(path, lockPath string) error
- func UpdateRecord(path, lockPath string, mutate func(rec *Record) error) error
- func WriteRecord(path, lockPath string, rec Record) error
- type API
- type Check
- type Client
- func (c *Client) Close(ctx context.Context, number int, comment string) error
- func (c *Client) Comment(ctx context.Context, number int, body string) error
- func (c *Client) Create(ctx context.Context, head, base, title, body string) (Ref, error)
- func (c *Client) Edit(ctx context.Context, number int, title, body string) error
- func (c *Client) FindOpen(ctx context.Context, head, base string) (Ref, bool, error)
- func (c *Client) Merge(ctx context.Context, number int, expectedHead string, squash bool, ...) error
- func (c *Client) Reopen(ctx context.Context, number int) error
- func (c *Client) RequiredChecks(ctx context.Context, base, head string) ([]Check, error)
- func (c *Client) RequiresPullRequest(ctx context.Context, branch string) (bool, error)
- func (c *Client) SetDraft(ctx context.Context, number int, draft bool) error
- func (c *Client) Status(ctx context.Context, number int) (Status, error)
- type MergeRefusal
- type MergeState
- type Record
- type Ref
- type RefusalKind
- type State
- type Status
Constants ¶
const CheckMissing = "missing"
CheckMissing is the conclusion of a required check that neither a check run nor a commit status carries at the head.
const MergeRetryWindow = time.Minute
MergeRetryWindow is how long a merge is retried. A read of a pull request whose mergeability GitHub has not computed is retried for the same length of time.
Variables ¶
var ErrNoRecord = errors.New("pullrequest: no pull request record")
ErrNoRecord is returned when the record's file does not exist.
Functions ¶
func RemoveRecord ¶
RemoveRecord deletes the record's file under the lock at lockPath; a file already absent is no error.
func UpdateRecord ¶
UpdateRecord applies mutate to the record at path under one exclusive lock and writes the result. It returns ErrNoRecord and writes nothing when the file does not exist, and writes nothing when mutate fails. A key of the file that Record does not declare survives the rewrite.
func WriteRecord ¶
WriteRecord writes rec to path under the lock at lockPath. A key of the replaced file that Record does not declare survives the write.
Types ¶
type API ¶
type API interface {
// FindOpen returns the first open pull request from head into base, and false when there is none.
FindOpen(ctx context.Context, head, base string) (Ref, bool, error)
// Create opens a pull request from head into base.
Create(ctx context.Context, head, base, title, body string) (Ref, error)
// Edit sets the title and body of an open pull request.
Edit(ctx context.Context, number int, title, body string) error
// Status reads a pull request's state, head and standing reviews.
Status(ctx context.Context, number int) (Status, error)
// Close comments on a pull request, then closes it without merging.
Close(ctx context.Context, number int, comment string) error
// Reopen reopens a pull request closed without merging, so it can be merged.
Reopen(ctx context.Context, number int) error
// SetDraft turns an open pull request into a draft, or a draft back into one ready for review.
SetDraft(ctx context.Context, number int, draft bool) error
// Comment posts one comment on a pull request.
Comment(ctx context.Context, number int, body string) error
// Merge merges a pull request whose head is expectedHead, squashing when squash is set.
// A merge GitHub refuses returns a *MergeRefusal.
Merge(ctx context.Context, number int, expectedHead string, squash bool, title, message string) error
// RequiresPullRequest reports whether a repository rule active on branch requires a pull request.
// Such a rule makes GitHub refuse a direct push to branch.
RequiresPullRequest(ctx context.Context, branch string) (bool, error)
// RequiredChecks returns the status checks the repository's rules for base require, each with its conclusion at head.
RequiredChecks(ctx context.Context, base, head string) ([]Check, error)
}
API is the set of GitHub calls a caller makes on its run's pull request, so a test fakes GitHub behind it.
type Check ¶
type Check struct {
// Name is the check's name, the context a rule requires.
Name string
// Conclusion is the check's conclusion at the head:
// empty while it has not finished, and CheckMissing when no check run or commit status of it exists there.
Conclusion string
}
Check is one status check a repository's rules require, with its outcome at a head commit.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client reaches the pull requests of one GitHub repository through an authenticated client. It is safe for concurrent use.
func NewWithClient ¶
NewWithClient returns the Client of owner/repo reached through gh.
func Open ¶
Open returns the Client of the GitHub repository originURL names, authenticated through githubclient. It refuses a URL that does not name a GitHub repository, naming the URL.
func (*Client) Close ¶
Close posts one comment on a pull request, then closes it without merging. When the comment posts and the close fails, the error says so, since a rerun posts a second comment.
func (*Client) Create ¶
Create opens a pull request from head into base with the given title and body.
func (*Client) FindOpen ¶
FindOpen returns the first open pull request from head into base, and false when there is none.
func (*Client) Merge ¶
func (c *Client) Merge(ctx context.Context, number int, expectedHead string, squash bool, title, message string) error
Merge merges a pull request whose head must equal expectedHead, squashing when squash is set. It uses title as the commit title and message as the commit message. Only HTTP 405 and 409 are read as a refusal. Any other failure is returned wrapped, so a caller can still judge whether it is transient.
func (*Client) RequiredChecks ¶
RequiredChecks reads every page of the rules active on base for the status-check contexts they require. It then matches each context to its outcome at head. A context's conclusion comes from the check run of that name, else from the commit status of that context. A pending commit status reads as unfinished, with an empty conclusion. A context with neither is reported as missing. When base requires no status checks, it returns no checks, without reading check runs or commit statuses.
func (*Client) RequiresPullRequest ¶
RequiresPullRequest reports whether the repository rules active on branch include a pull_request rule. It reads the rules one page at a time and stops at the first page with a pull_request rule.
type MergeRefusal ¶
type MergeRefusal struct {
// Kind classifies the refusal from GitHub's message.
Kind RefusalKind
// Message is GitHub's own message.
Message string
}
MergeRefusal is the error of a merge GitHub refused with HTTP 405 or 409.
func (*MergeRefusal) Error ¶
func (r *MergeRefusal) Error() string
Error returns GitHub's message, prefixed with a note that GitHub refused the merge.
type MergeState ¶
type MergeState struct {
// State is where the pull request stands.
State State
// Draft reports whether the pull request is a draft.
Draft bool
// Head is the pull request's current head commit.
Head string
// MergeableState is GitHub's mergeable_state of the pull request.
MergeableState string
// Approved reports whether a standing review approves Head.
Approved bool
// Checks are the required checks of the base branch, each with its conclusion at Head.
Checks []Check
// MergeableNow reports whether GitHub would merge the pull request now.
// It must be open, not a draft, at the beam's head, and in a mergeable state of clean, has_hooks or unstable.
MergeableNow bool
// Causes name what stops the merge, one per cause; empty exactly when MergeableNow.
Causes []string
}
MergeState is a pull request's state as read from GitHub, with the judgment of whether it can merge now.
func ReadMergeState ¶
func ReadMergeState(ctx context.Context, api API, number int, beamHead, base string, now func() time.Time, sleep func(ctx context.Context, d time.Duration) error) (MergeState, error)
ReadMergeState reads pull request number and the required checks of base at its head. It judges whether the pull request can merge now, with beamHead as the head it must be at. A mergeable_state that GitHub has not yet computed is read again after sleep, doubling from one second. Reads stop once MergeRetryWindow of the now clock has passed since the first read, and the unknown state is then reported as a cause. A failed GitHub call, and a failed sleep, are returned.
type Record ¶
type Record struct {
// Number is the pull request's number and the one field always written.
Number int `json:"number"`
// URL is the pull request's page on GitHub.
URL string `json:"url,omitempty"`
// Base is the branch the pull request merges into.
Base string `json:"base,omitempty"`
// Head is the task branch the pull request merges from.
Head string `json:"head,omitempty"`
// GatedHead is the head the PR-Review row last gated and pushed.
GatedHead string `json:"gated_head,omitempty"`
// MailedHead is the head whose gate failure was mailed, so one failure is mailed once.
MailedHead string `json:"mailed_head,omitempty"`
// StrandGUID, StrandName, SessionID and Engine name the interactive PR-Review session, so a later pass resumes it.
StrandGUID string `json:"strand_guid,omitempty"`
StrandName string `json:"strand_name,omitempty"`
SessionID string `json:"session_id,omitempty"`
Engine string `json:"engine,omitempty"`
// Continue is the operator's continue flag, set by `lym loom pr-continue`.
Continue bool `json:"continue,omitempty"`
// ReviewEnded records that the review ended.
// The PR-Review row sets it when it closes the pull request on the continue flag.
// Finalize sets it on the record it opens for a parent that takes its pull request with no review.
// In that case the pull request is left for Finalize to merge.
// The JSON key stays `closed_for_continue`, so a record written by an older build reads the same.
ReviewEnded bool `json:"closed_for_continue,omitempty"`
// ParentReviewAsked records that the PR-Review row asked the parent orch to review this pull request.
ParentReviewAsked bool `json:"parent_review_asked,omitempty"`
}
Record is the state a run keeps about its pull request, one JSON file in the run's scratch directory.
type RefusalKind ¶
type RefusalKind string
RefusalKind classifies why GitHub refused a merge.
const ( // RefusalMethod means the repository does not allow the merge method asked for. RefusalMethod RefusalKind = "method" // RefusalPending means the base branch moved or GitHub is still computing mergeability, so a retry may succeed. RefusalPending RefusalKind = "pending" // RefusalOther covers every other refusal: // a moved head, a missing approval, a required check or a conflict, none of which a retry clears. RefusalOther RefusalKind = "other" )
type Status ¶
type Status struct {
State State
// HeadSHA is the pull request's current head commit.
HeadSHA string
// Approved reports whether a reviewer's standing review approves HeadSHA.
Approved bool
// ChangesRequested reports whether any reviewer's standing review requests changes.
ChangesRequested bool
// Draft reports whether the pull request is a draft, which GitHub refuses to merge.
Draft bool
// MergeableState is GitHub's mergeable_state of the pull request:
// clean, dirty, blocked, behind, unstable, has_hooks, unknown or draft.
MergeableState string
}
Status is a pull request's state, head commit and standing review verdicts as GitHub reports them.