pullrequest

package
v0.0.0-...-2ad39d2 Latest Latest
Warning

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

Go to latest
Published: Nov 9, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

View Source
const CheckMissing = "missing"

CheckMissing is the conclusion of a required check that neither a check run nor a commit status carries at the head.

View Source
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

View Source
var ErrNoRecord = errors.New("pullrequest: no pull request record")

ErrNoRecord is returned when the record's file does not exist.

Functions

func RemoveRecord

func RemoveRecord(path, lockPath string) error

RemoveRecord deletes the record's file under the lock at lockPath; a file already absent is no error.

func UpdateRecord

func UpdateRecord(path, lockPath string, mutate func(rec *Record) error) error

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

func WriteRecord(path, lockPath string, rec Record) error

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

func NewWithClient(gh *github.Client, owner, repo string) *Client

NewWithClient returns the Client of owner/repo reached through gh.

func Open

func Open(originURL string) (*Client, error)

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

func (c *Client) Close(ctx context.Context, number int, comment string) error

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) Comment

func (c *Client) Comment(ctx context.Context, number int, body string) error

Comment posts body as one comment on a pull request.

func (*Client) Create

func (c *Client) Create(ctx context.Context, head, base, title, body string) (Ref, error)

Create opens a pull request from head into base with the given title and body.

func (*Client) Edit

func (c *Client) Edit(ctx context.Context, number int, title, body string) error

Edit sets the title and body of a pull request.

func (*Client) FindOpen

func (c *Client) FindOpen(ctx context.Context, head, base string) (Ref, bool, error)

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) Reopen

func (c *Client) Reopen(ctx context.Context, number int) error

Reopen sets a closed pull request's state back to open.

func (*Client) RequiredChecks

func (c *Client) RequiredChecks(ctx context.Context, base, head string) ([]Check, error)

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

func (c *Client) RequiresPullRequest(ctx context.Context, branch string) (bool, error)

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.

func (*Client) SetDraft

func (c *Client) SetDraft(ctx context.Context, number int, draft bool) error

SetDraft converts a pull request to a draft when draft is true, and marks it ready for review when draft is false. GitHub's REST API does not set either state. SetDraft therefore reads the pull request's node id and runs the GraphQL mutation that sets it.

func (*Client) Status

func (c *Client) Status(ctx context.Context, number int) (Status, error)

Status reads a pull request's state, with merged winning over closed, and its head commit. It reads the standing verdict across every page of its reviews.

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.

func ReadRecord

func ReadRecord(path, lockPath string) (Record, bool, error)

ReadRecord reads the record at path under the lock at lockPath. It reports false when the file does not exist.

type Ref

type Ref struct {
	Number int
	URL    string
}

Ref names a pull request.

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 State

type State string

State is where a pull request stands on GitHub.

const (
	// StateOpen is a pull request that is neither closed nor merged.
	StateOpen State = "open"
	// StateClosed is a pull request closed without merging.
	StateClosed State = "closed"
	// StateMerged is a merged pull request, which GitHub also lists as closed.
	StateMerged State = "merged"
)

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.

Jump to

Keyboard shortcuts

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