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

loop

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package loop composes the developer loop once, for every surface: the pull request a branch proposes and the push it needs first, the review status it moves the issue to, the announcement of where it stands and the memory of what was announced before, what "stage all" stages, the scope a commit remembers, and the guards a switch or a commit respects. The command line, the terminal interface and the web server each hand it their seams — plain functions over the domain clients — and word its refusals in their own terms.

It sits below the three surfaces and above the domain packages, and imports nothing that imports it back. The depguard rule loop-below-the-surfaces in .golangci.yml lists what it may import and holds that direction.

Index

Constants

View Source
const NoCIPatience = 10 * time.Minute

NoCIPatience is how long an announcement armed to go once CI passes waits for a pull request that reports no check, counted from the last read that reported one, or from arming, before it gives up.

Trade-off TRADE-41: a CI that registers later than this loses its automatic post, so a repository with no CI is not polled for as long as it waits.

View Source
const NotRememberedWarning = "Posted, but not remembered: it may be offered again."

NotRememberedWarning is what every surface says of an announcement that Deliver posted but answered ErrNotRemembered for, after the line saying where it went. The terminal and workflow announce follow it with why; the web leaves why to the server's log, since the store's error can name its file.

Variables

View Source
var (
	// ErrNoPullRequest refuses an announcement when the branch has no pull
	// request to announce, or there is no way to find one.
	ErrNoPullRequest = errors.New("there is no pull request to announce")
	// ErrAnnounceUnavailable refuses an announcement with no way to post one.
	ErrAnnounceUnavailable = errors.New("announcing is not available")
	// ErrNotRemembered reports an announcement that was posted but could not
	// be remembered, so a later session may offer to post it again. It says
	// NotRememberedWarning's sentence, as an error says it, so the --web
	// server's log words it as every surface does.
	ErrNotRemembered = errors.New("posted, but not remembered: it may be offered again")
	// ErrEmptyAnnouncement refuses an announcement edited down to nothing:
	// guidance, not a failure.
	ErrEmptyAnnouncement = errors.New("nothing to announce: the message was empty")
	// ErrCIFailed refuses an announcement of a pull request a check failed
	// on: what it announces is not ready for anyone to look at.
	ErrCIFailed = errors.New("a check failed on the pull request")
	// ErrCIUnread refuses an announcement whose pull request's CI could not
	// be read, so nothing failing on it could not be confirmed. It wraps the
	// read's own error.
	ErrCIUnread = errors.New("the pull request's CI could not be read")
)
View Source
var (
	// ErrNoCI gives up on an armed announcement whose pull request reported no
	// check within NoCIPatience: it is announced by hand.
	ErrNoCI = errors.New("no checks were reported on the pull request")
	// ErrPullChanged gives up on an armed announcement whose branch's pull
	// request is another one, or none.
	ErrPullChanged = errors.New("the branch's pull request is no longer the one announced")
	// ErrMergedFirst gives up on an armed announcement whose pull request
	// merged before its CI passed.
	ErrMergedFirst = errors.New("the pull request merged before its CI passed")
	// ErrNotListedYet is a find that does not see a pull request just opened,
	// which a forge may list only a moment later: a read to make again.
	ErrNotListedYet = errors.New("the forge does not list the pull request yet")
)
View Source
var (
	// ErrDirtyTree refuses switching branches with uncommitted work in the tree,
	// which the switch would carry onto the other branch. Stashing is left to the
	// person, so the one sentence every surface tells says what to do.
	ErrDirtyTree = errors.New("the working tree has uncommitted changes; commit or stash them before switching")
	// ErrNothingStaged refuses a commit with nothing in the index.
	ErrNothingStaged = errors.New("nothing is staged to commit")
)
View Source
var (
	// ErrNotOnFeatureBranch refuses finishing the base itself or a detached
	// HEAD: there is no branch of its own to delete.
	ErrNotOnFeatureBranch = errors.New("no branch of its own is checked out")
	// ErrNotMerged refuses finishing a branch whose pull request has not
	// merged: none was found, it is still open, or it closed without merging.
	ErrNotMerged = errors.New("it has not merged")
	// ErrNoBase refuses finishing a branch whose base was not found, so there
	// is nowhere to switch to.
	ErrNoBase = errors.New("there is no base branch to return to")
	// ErrUnpushedWork refuses finishing a branch whose commits nothing else is
	// known to hold, which the force delete would lose: it is not at the
	// commit that merged, and it has no upstream or is ahead of the one it has.
	ErrUnpushedWork = errors.New(
		"it holds commits that neither merged nor are on its upstream, which deleting it would lose; push them first")
)

Why a merged branch cannot be finished, each worded to follow "cannot finish BRANCH: " in any forge's words.

View Source
var (
	// ErrNothingToOpen refuses composing a pull request when there is nothing to
	// propose: the tree is not on a branch, the branch has no commits, or there
	// is no way to read either.
	ErrNothingToOpen = errors.New("there is nothing to open a pull request for")
	// ErrPullAlreadyOpen refuses a second pull request for a branch that already
	// has an open one. The error returned is a PullAlreadyOpenError, which carries
	// that pull request.
	ErrPullAlreadyOpen = errors.New("a pull request is already open for this branch")
)
View Source
var (
	// ErrPushFailed is a push that ran and did not publish the branch. The error
	// returned is a PushFailedError, which carries the push's own output.
	ErrPushFailed = errors.New("the push failed")
	// ErrPushUnavailable refuses a push with no way to make one.
	ErrPushUnavailable = errors.New("pushing is not available")
	// ErrFetchFailed is a fetch of the base remote that failed before a new
	// branch was made from what it fetched; nothing was made.
	ErrFetchFailed = errors.New("the base remote could not be fetched")
)
View Source
var (
	// ErrNoReviewStatus is a configuration with no jira.review_status to move to.
	ErrNoReviewStatus = errors.New("no review status is configured (jira.review_status)")
	// ErrNoTransition is an issue the tracker offers no move to the status
	// for, from where it stands — or no issue, or no tracker to ask.
	ErrNoTransition = errors.New("no move to that status is offered")
	// ErrNeedsFields is a move that asks for fields, which a move made without
	// a form cannot fill.
	ErrNeedsFields = errors.New("the move asks for fields")
)

Why no move to a status is made, for a surface that answers a request for one rather than offering it.

View Source
var ErrEmptySummary = errors.New("nothing to post: the summary was empty")

ErrEmptySummary refuses a Summary edited down to nothing: there is nothing to post.

View Source
var ErrGroupNotOffered = errors.New("that user group is not offered for this announcement")

ErrGroupNotOffered refuses to tag a user group the announcement did not offer.

View Source
var ErrNoSuchTemplate = errors.New("the repository has no pull request template of that name")

ErrNoSuchTemplate refuses a pull request template asked for by a name the repository has none of.

View Source
var ErrStagingUnavailable = errors.New("staging is not available")

ErrStagingUnavailable refuses staging with no way to stage.

View Source
var ErrSummaryUnavailable = errors.New("posting the summary is not available")

ErrSummaryUnavailable refuses a Summary post with no way to make one.

View Source
var ErrUnknownWorkspace = errors.New("can't tell which Slack workspace this token is for")

ErrUnknownWorkspace reports a Slack user token whose workspace could not be read. Links are kept per workspace, so nobody is tagged: the announcement posts untagged, and a surface says so with why.

Functions

func AnnounceMoment

func AnnounceMoment(pull forge.PullRequest) messaging.Moment

AnnounceMoment is the moment a pull request is at: merged, or — the common case — open and ready for review. Its CI decides whether it may be announced (see ClearToAnnounce), never what is said of it.

func Announcement added in v0.4.0

func Announcement(facts AnnouncementFacts, cfg config.Messaging, kind forge.Kind) messaging.Announcement

Announcement is the announcement of facts a surface already holds, rendered for the configured service and template, and calling the change what the forge of kind calls it. ComposeAnnouncement reads the facts through seams; a surface that holds them already, as the terminal does, passes them here.

func AssignedKeys added in v0.4.1

func AssignedKeys(
	ctx context.Context, search func(ctx context.Context, jql string, startAt int) (jira.SearchResult, error),
	keys []jira.Key,
) (map[jira.Key]bool, error)

AssignedKeys is the subset of keys naming issues assigned to whoever the credential belongs to, asked of the tracker in batches a URL can hold and in pages of its size. No keys asks nothing. A forge issue's number is never named in the query, since Jira would read it as the id of an unrelated issue: the forge's own issues ignore the query and answer every assigned issue, alone or ahead of Jira's, so the intersection finds them all the same, and keys that are all forge numbers are asked once with NoKeys. Every search is made under ctx.

func CanMerge added in v0.7.0

func CanMerge(pull forge.PullRequest, checks forge.CI) bool

CanMerge reports a pull request ready to merge: open, not a draft, free of conflicts, approved with no changes asked for, and its CI green.

func CanRebase added in v0.7.0

func CanRebase(branch gitrepo.Branch) bool

CanRebase reports a feature branch with a base to replay it onto.

func CanRerun added in v0.7.0

func CanRerun(pull forge.PullRequest, checks forge.CI) bool

CanRerun reports failed CI on an open pull request. A merged one is left alone even when a stale read still says failed: nothing needs re-running once it is in.

func ClearToAnnounce added in v0.10.0

func ClearToAnnounce(
	ctx context.Context, check func(context.Context, forge.PullRequest, string) (forge.CI, error),
	pull forge.PullRequest, head string,
) (forge.CI, error)

ClearToAnnounce reads pull's CI at head under ctx and answers it when nothing has failed on it: none reported, running or passed. A failed check is ErrCIFailed; a CI that cannot be read, or no way to read it, is ErrCIUnread, wrapping the read's error, since nothing failing cannot be confirmed.

func CommitsRead added in v0.6.0

func CommitsRead(
	ctx context.Context, read func(ctx context.Context, start, end time.Time) []RepositoryCommits, start, end time.Time,
) activity.Read

CommitsRead is your commits from start up to end as the Summary reads them, each named by its repository when there is more than one. A commit two repositories share — a fork, a second clone — is read once, from the first; a repository that cannot be read is named in the failure, and the others' commits are still read. The repositories are read under ctx.

func ComposeAnnouncementOf added in v0.10.0

func ComposeAnnouncementOf(
	ctx context.Context, seams AnnounceSeams, branch gitrepo.Branch, pull forge.PullRequest,
	cfg config.Messaging, project string, kind forge.Kind,
) messaging.Announcement

ComposeAnnouncementOf builds the announcement of pull, opened from branch, as ComposeAnnouncement does, without asking the forge to find it: for a pull request just opened, which a fresh lookup might not find yet. It reads the author and the issue under ctx, and says less where they cannot be read.

func ComposePull

func ComposePull(
	ctx context.Context, seams PullSeams, opts PullOptions,
) (forge.NewPullRequest, gitrepo.Branch, error)

ComposePull proposes the pull request for the checked-out branch — a title and body from its commits, its issue and the repository's template opts names, or its first, the base it would merge into, and the code owners of its changes as reviewers — and returns the branch it read. It refuses with ErrNothingToOpen when there is nothing to propose, with ErrNoSuchTemplate when the repository has no template of the name opts gives, and with a PullAlreadyOpenError when an open pull request already stands for the branch. A merged or closed one does not stand in the way, and neither does a forge that cannot say: the open is where that forge's own answer lands.

func Deliver

func Deliver(ctx context.Context, seams DeliverSeams, memory AnnounceMemory, delivery Delivery) error

Deliver posts the delivery under ctx, its tags on a line after the text, once ClearToAnnounce finds nothing failed on its pull request, and, once it has gone out, records what it made, so a later session knows not to make it again, and the groups it tagged, whether or not ctx has ended since. A refusal or a post that fails records nothing; the error is the gate's or the post's own, for the caller to word. A post that went out but was not remembered is ErrNotRemembered, carrying why, which NotRememberedReason reads: the post was made, and the caller says so with NotRememberedWarning.

func Draft added in v0.4.0

func Draft(input DraftInput) (string, string)

Draft proposes a pull request's title, then its body, from what a surface already holds. ComposePull reads the input through seams and the template its options name; a surface that holds it already, as the terminal's composer does, passes it here.

func EnsurePushed

func EnsurePushed(
	ctx context.Context, push func(ctx context.Context, branch string) (proc.Output, error), branch gitrepo.Branch,
) error

EnsurePushed publishes the branch when its remote does not have it yet, since a pull request cannot open from an unpushed branch. A pushed branch is left alone. The push runs under ctx.

func Failures added in v0.6.0

func Failures(err error) []error

Failures is each failure a read's error holds: each repository's when CommitsRead read several, the error itself otherwise, none when there is none.

func FetchThen added in v0.7.0

func FetchThen(ctx context.Context, fetch func(ctx context.Context) error, create func() error) error

FetchThen fetches the base remote, so a new branch starts from what it holds now, then makes it through create. A nil fetch — no base to refresh, or the person chose to branch from what they have — makes it at once. A fetch that fails makes nothing and is ErrFetchFailed, wrapping the fetch's own error for a surface that may show it. The fetch runs under ctx.

func FindReviewTransition

func FindReviewTransition(
	ctx context.Context, transitions func(context.Context, jira.Key) ([]jira.Transition, error), key jira.Key,
	status string,
) (jira.Transition, error)

FindReviewTransition is ReviewTransition saying why there is no move: no review status configured, no issue or tracker to ask, or TransitionTo's refusal. With no status configured the tracker is not asked at all; otherwise it is asked under ctx.

func Foldable added in v0.7.0

func Foldable(changes []gitrepo.Change, branch gitrepo.Branch) []gitrepo.Commit

Foldable is the commits staged changes can be folded into — amended into the last, or fixed up into any — oldest first: the branch's commits not yet pushed, which alone are safe to rewrite, and none while nothing is staged.

func ForgeRead added in v0.6.0

func ForgeRead(
	ctx context.Context, read func(ctx context.Context, start, end time.Time) (forge.Activity, error),
	kind forge.Kind, start, end time.Time,
) activity.Read

ForgeRead is what you did on the forge from start up to end, asked under ctx, each pull or merge request named by its repository and number as kind writes it.

func IssueOf added in v0.5.0

func IssueOf(branch gitrepo.Branch, project string) (convention.IssueRef, bool)

IssueOf is the issue branch is for by its link or its name, where no pull request is at hand to ask.

func JiraIssue added in v0.4.0

func JiraIssue(branch gitrepo.Branch, project string) (jira.Key, bool)

JiraIssue is the Jira issue the branch is for, by its link or its name, and whether it has one. The bare forge issue number a branch can carry instead, 42, is no Jira issue even with Jira as the tracker.

func JiraRead added in v0.6.0

func JiraRead(
	ctx context.Context, read func(ctx context.Context, start, end time.Time) (jira.Activity, error),
	browse func(jira.Key) string, start, end time.Time,
) activity.Read

JiraRead is what you did to Jira issues from start up to end, asked under ctx, each linked to its page as browse names it.

func NamedIssue added in v0.5.0

func NamedIssue(name string, links map[string]string, project string) (convention.IssueRef, bool)

NamedIssue is the issue a branch known only by its name is for: the one it was linked to, in links, or else the one its name names.

func NotRememberedReason added in v0.9.0

func NotRememberedReason(err error) (string, bool)

NotRememberedReason reports whether err is Deliver's word that it posted an announcement the store could not remember, wrapped or not, and why the store could not: what the terminal and workflow announce say after NotRememberedWarning.

func NotSetUp added in v0.7.0

func NotSetUp(err error) bool

NotSetUp reports an error that says a service was never set up to ask, as against one that was asked and refused: Jira or the forge has no credential, the base remote names no forge workflow reads, no Taskwarrior is installed or it has never run, or git has no user.email. Every surface tells it as guidance — what to set up — rather than as a failure.

func OnFeatureBranch added in v0.7.0

func OnFeatureBranch(branch gitrepo.Branch) bool

OnFeatureBranch reports a branch of its own, not a detached HEAD and not the base itself: the branch a rebase, a finish or a pull request is about.

func OwnersOf added in v0.5.0

func OwnersOf(ctx context.Context, seams OwnerSeams, base string) (codeowners.Owners, error)

OwnersOf is who owns the paths the branch changes since base, as CODEOWNERS on base names them, without the author: nobody is asked to review their own pull request. With no CODEOWNERS on base, or no way to read one, it is nobody. The forge is asked under ctx.

func PostSummary added in v0.7.0

func PostSummary(
	ctx context.Context, post func(ctx context.Context, channel, text string) error, kind config.MessagingKind,
	channel, text string,
) error

PostSummary posts text — the Summary's Markdown, as it was previewed or edited — to channel, rendered for the service kind names. A text longer than that service takes is refused with messaging.ErrTooLong, saying how long it is against what, before anything is sent, and no post at all is ErrSummaryUnavailable, as Deliver and Push refuse theirs.

func ProposedReviewers added in v0.5.0

func ProposedReviewers(ctx context.Context, seams OwnerSeams, base string) []string

ProposedReviewers is the owners of the branch's changes as the reviewers a pull request proposes: people, then teams as org/team. It is a proposal, so a failure to read the owners proposes nobody rather than holding the pull request back. A GitLab group stays group/subgroup here: the forge expands it to its members when the merge request is opened. The forge is asked under ctx.

func Push

func Push(
	ctx context.Context, push func(ctx context.Context, branch string) (proc.Output, error), branch string,
) error

Push publishes branch through push, under ctx, draining its output so the push runs to completion. A push that ran and failed is a PushFailedError; one that could not start returns the seam's own error, for the caller to word.

func ReadAll added in v0.7.0

func ReadAll(ctx context.Context, reads []SourceRead) []activity.Read

ReadAll makes each read in turn under ctx, for a surface that shows the Summary once every source has answered.

func RefuseDirty

func RefuseDirty(changes []gitrepo.Change) error

RefuseDirty refuses a working tree that holds any change — staged, unstaged or untracked — before a switch moves it onto another branch.

func RefuseFinish added in v0.10.0

func RefuseFinish(pull forge.PullRequest, found bool, branch gitrepo.Branch) error

RefuseFinish says why a branch cannot be finished, or nil when it can: it is checked out as a branch of its own, its pull request — found says whether there is one — has merged, it has a base to return to, and the force delete would lose nothing (see keptElsewhere).

func RefuseNothingStaged

func RefuseNothingStaged(changes []gitrepo.Change) error

RefuseNothingStaged refuses a commit when no change is in the index, before it runs and fails with git's own less helpful message.

func RememberScope added in v0.4.0

func RememberScope(
	ctx context.Context, record func(ctx context.Context, scope string) error, scope string,
) (bool, error)

RememberScope hands record the scope a commit just used, trimmed as the subject line writes it, and reports whether it did, with why record could not keep it. A blank scope is not recorded: it would erase the scope learned before and hide the configured default the next commit opens on. Nothing is recorded without a record to hand it to, as when the store is off. It is recorded under ctx.

func ReviewTransition

func ReviewTransition(
	ctx context.Context, transitions func(context.Context, jira.Key) ([]jira.Transition, error), key jira.Key,
	status string,
) (jira.Transition, bool)

ReviewTransition is the move to the configured review status for an issue whose pull request is now open, and whether there is one worth offering: the status is configured, the branch names an issue, Jira offers a move to that status, and the move needs no fields. A tracker that cannot be read offers nothing — the pull request is already open. The tracker is asked under ctx.

func SameOwner added in v0.5.0

func SameOwner(one, other string) bool

SameOwner reports whether two forge owner names name the same user or team: both forges read them without regard to case, so a CODEOWNERS file may spell an owner differently from the name a link was saved under.

func SetUpAdvice added in v0.7.0

func SetUpAdvice(err error) (string, bool)

SetUpAdvice is how to set up what err says was never set up, in the words every surface tells it in — the web's problem, the command line's note and the terminal's guidance — reporting false for an error NotSetUp does not match. The words never name a host or a path, so a surface that must not show one can show them; one that may shows the error's own beneath.

func SplitReviewers added in v0.5.0

func SplitReviewers(names []string) ([]string, []string)

SplitReviewers tells the teams among reviewer names from the people: a name holding a slash is a team (org/team, or a GitLab group/subgroup).

func StageAll

func StageAll(ctx context.Context, changes []gitrepo.Change, stage func(context.Context, gitrepo.Change) error) error

StageAll stages every Stageable change through stage, going on past a file git refuses so one failure does not hold back the rest, and reports every failure joined. Each change is staged under ctx.

func Stageable

func Stageable(changes []gitrepo.Change) []gitrepo.Change

Stageable is every change "stage all" takes into the index: work the index does not hold yet — an edit, a deletion, a file git does not track, the rest of a partly staged file — and a conflict, which staging marks resolved. What is wholly staged already is left out.

func StillArmed added in v0.10.0

func StillArmed(number int, pull forge.PullRequest, found bool) error

StillArmed reports whether an announcement armed for pull request number still stands, given the branch's pull request now: ErrPullChanged when it is another one, or none — one closed without merging counts as none — and ErrMergedFirst when it merged before its CI passed.

func Subjects

func Subjects(commits []gitrepo.Commit) []string

Subjects is the subject line of each commit, oldest first, which is what a pull request's title and body are proposed from.

func SummaryLength added in v0.7.0

func SummaryLength(kind config.MessagingKind, text string) messaging.Length

SummaryLength is text's length as PostSummary would post it for kind, against the most kind's service takes, for a preview to show before the post.

func TagWorkspace added in v0.5.0

func TagWorkspace(ctx context.Context, read func(ctx context.Context) (string, error)) (string, error)

TagWorkspace is the Slack workspace tags are read and kept under, as read says under ctx: messaging.ErrNoCredential when there is no Slack user token to tag with, as with no read at all, and ErrUnknownWorkspace, wrapping why, when Slack cannot say which it is.

func TasksRead added in v0.6.0

func TasksRead(
	ctx context.Context, touched func(ctx context.Context, since time.Time) ([]taskwarrior.Task, error),
	start, end time.Time,
) activity.Read

TasksRead is what you did to tasks from start up to end, asked under ctx: each task touched since start reads as its adding, starting, notes and completing that fall in the period.

func TransitionTo added in v0.10.0

func TransitionTo(
	ctx context.Context, transitions func(context.Context, jira.Key) ([]jira.Transition, error), key jira.Key,
	status string,
) (jira.Transition, error)

TransitionTo is the move of key to the status of that name, matched without regard to case, as the tracker offers it from where the issue stands under ctx. A status not offered is a NoTransitionError naming those that are; a move that asks for fields is a NeedsFieldsError naming them; a tracker that cannot be read is its own error, saying what was read.

func UnstageAll

func UnstageAll(
	ctx context.Context, changes []gitrepo.Change, unstage func(context.Context, gitrepo.Change) error,
) error

UnstageAll takes every staged change out of the index through unstage — a partly staged file's staged part too, leaving its work tree edits alone — going on past a file git refuses, and reports every failure joined. Each change is unstaged under ctx.

Types

type ActivitySeams added in v0.7.0

type ActivitySeams struct {
	Commits   func(ctx context.Context, start, end time.Time) []RepositoryCommits
	Touched   func(ctx context.Context, since time.Time) ([]taskwarrior.Task, error)
	Jira      func(ctx context.Context, start, end time.Time) (jira.Activity, error)
	BrowseURL func(jira.Key) string
	Forge     func(ctx context.Context, start, end time.Time) (forge.Activity, error)
	ForgeKind forge.Kind
}

ActivitySeams are what the Summary asks each source: the commits you wrote, the tasks touched since a time, what you did to Jira issues — linked as BrowseURL links an issue — and on the forge, whose changes ForgeKind names. A nil read is a source the Summary does not ask.

type AnnounceMemory

type AnnounceMemory struct {
	Recorded     func(ctx context.Context) []Announced
	Record       func(ctx context.Context, made Announced) error
	RecordGroups func(ctx context.Context, ids []string) error
}

AnnounceMemory is what is remembered of the announcements made in this repository, in this session or an earlier one: every one recorded, and the record of a new one. A nil Recorded knows of none, and a nil Record remembers nothing.

RecordGroups remembers the user groups an announcement tagged, as this repository's last choice. A nil RecordGroups remembers none; a surface sets it only for an announcement that offered groups to choose, since a choice of none is a choice too.

func (AnnounceMemory) Holds

func (m AnnounceMemory) Holds(ctx context.Context, made Announced) bool

Holds reports whether made was already announced, as read under ctx.

func (AnnounceMemory) HoldsNow added in v0.9.0

func (m AnnounceMemory) HoldsNow(ctx context.Context, pull forge.PullRequest, found bool) bool

HoldsNow reports that a find answered a pull request the loop follows, and that it was announced at the moment it is at now: the one rule every surface reads the announce stage, and whether to say the pull request was announced, by. What was announced is read under ctx.

type AnnounceSeams

type AnnounceSeams struct {
	Branch    func(ctx context.Context) (gitrepo.Branch, error)
	FindPull  func(ctx context.Context, branch string) (forge.PullRequest, bool, error)
	Author    func(ctx context.Context) (string, error)
	Issue     func(ctx context.Context, issueKey jira.Key) (jira.IssueDetail, error)
	BrowseURL func(jira.Key) string
}

AnnounceSeams are what composing an announcement reads. A nil Branch or FindPull means there is nothing to announce; a nil Author, Issue or BrowseURL only makes the announcement say less.

type Announced

type Announced struct {
	Pull   int
	Moment messaging.Moment
}

Announced is one announcement made: a pull request, and the moment it marked. A pull request is announced once at each of its moments — ready for review, then merged — so the pair is what a later session is told was made.

type AnnouncementFacts added in v0.4.0

type AnnouncementFacts struct {
	Author       string
	Pull         forge.PullRequest
	IssueKey     jira.Key
	IssueSummary string
	IssueURL     string
	Moment       messaging.Moment
}

AnnouncementFacts are what an announcement tells: who opened the pull request, the pull request, the issue its branch names — empty fields where there is none, or the tracker could not say — and the moment it marks.

type Armed added in v0.10.0

type Armed int

Armed is how an announcement armed to go once CI passes stands on a read of its pull request's CI.

const (
	// ArmedWaiting waits while CI runs.
	ArmedWaiting Armed = iota
	// ArmedAwaitingChecks waits for the pull request to report a check.
	ArmedAwaitingChecks
	// ArmedAwaitingFix waits for a run that passes, a check having failed.
	ArmedAwaitingFix
	// ArmedDue is to post now: CI passed.
	ArmedDue
	// ArmedNoCI gives up: no check was reported within NoCIPatience.
	ArmedNoCI
)

type AwaitSeams added in v0.10.0

type AwaitSeams struct {
	Branch   func(ctx context.Context) (gitrepo.Branch, error)
	FindPull func(ctx context.Context, branch string) (forge.PullRequest, bool, error)
	CheckCI  func(ctx context.Context, pull forge.PullRequest, head string) (forge.CI, error)
	Pause    func(ctx context.Context, wait time.Duration) error
	Now      func() time.Time
	Interval time.Duration
	Delay    time.Duration
	Opened   bool
	Heard    func(Heard)
}

AwaitSeams are what AwaitGreen reads and waits through. Pause waits wait under ctx, answering ctx's error once it ends; Now tells the time patience is counted by. Heard, which may be nil, is told of each read. Delay, when set, is waited before the first read, as for a wait taken up again. Opened is set for a pull request opened a moment ago, which the forge may not list yet: until a find has seen it, one that does not is heard as a read that failed, ErrNotListedYet, and read again.

type CIWatch added in v0.10.0

type CIWatch struct {
	// contains filtered or unexported fields
}

CIWatch is the watch over an armed announcement's CI: when a read last reported a check, which its patience with none counts from.

func WatchFrom added in v0.10.0

func WatchFrom(armedAt time.Time) CIWatch

WatchFrom is the watch of an announcement armed at armedAt.

func (CIWatch) Read added in v0.10.0

func (w CIWatch) Read(ci forge.CI, readAt time.Time) (CIWatch, Armed)

Read is how the armed announcement stands on ci, read at readAt, and the watch to read the next one with: a check reported, running, failed or passed, starts its patience again.

type CommentMarkup added in v0.6.0

type CommentMarkup int

CommentMarkup is what a comment on an issue is written in, and so what it is posted as.

const (
	// MarkupWiki is Jira's own wiki markup, posted as typed.
	MarkupWiki CommentMarkup = iota + 1
	// MarkupJiraMarkdown is Markdown, posted to Jira as the wiki markup
	// jira.WikiFromMarkdown makes of it.
	MarkupJiraMarkdown
	// MarkupForgeMarkdown is Markdown posted to a forge as typed, since the
	// forge renders Markdown itself.
	MarkupForgeMarkdown
)

func CommentMarkupOf added in v0.6.0

func CommentMarkupOf(settings config.Jira, issueKey jira.Key) CommentMarkup

CommentMarkupOf is the markup a comment on issueKey is written in: Markdown on a forge issue, and on a Jira issue as settings say, read as the surface asking reads them now, so a Settings save applies to the next comment.

func (CommentMarkup) Stored added in v0.6.0

func (m CommentMarkup) Stored(text string) string

Stored is text as its tracker will store it, which is what a surface shows for a last look and then posts unchanged.

type Composed added in v0.10.0

type Composed struct {
	Announcement messaging.Announcement
	Pull         forge.PullRequest
	Branch       gitrepo.Branch
}

Composed is an announcement composed for the checked-out branch's pull request, the pull request it announces, and the branch it was read from, whose head is the commit the pull request's CI is read at.

func ComposeAnnouncement

func ComposeAnnouncement(
	ctx context.Context, seams AnnounceSeams, cfg config.Messaging, project string, kind forge.Kind,
) (Composed, error)

ComposeAnnouncement builds the announcement for the checked-out branch's pull request — its title and link, the branch's issue, who opened it and the moment it is at — rendered for the configured service and template, with the pull request it announces and the branch it read, reading under ctx. It refuses with ErrNoPullRequest when there is none, and says which read failed when the branch or the pull request cannot be read.

func (Composed) Delivery added in v0.10.0

func (c Composed) Delivery(channel, text string, mentions messaging.Mentions) Delivery

Delivery is the composed announcement on its way out as text, to channel, tagging mentions.

func (Composed) Made added in v0.10.0

func (c Composed) Made() Announced

Made is the announcement posting the composed one makes: its pull request at its moment.

type DeliverSeams added in v0.10.0

type DeliverSeams struct {
	Post    func(ctx context.Context, channel, text string) error
	CheckCI func(ctx context.Context, pull forge.PullRequest, head string) (forge.CI, error)
}

DeliverSeams are what a delivery goes out through: the post, and the read of the CI the gate makes before it. A nil Post cannot announce; a nil CheckCI cannot confirm that nothing failed, so it refuses too.

type Delivery

type Delivery struct {
	Channel  string
	Text     string
	Pull     forge.PullRequest
	Head     string
	Moment   messaging.Moment
	Mentions messaging.Mentions
}

Delivery is an announcement on its way out: the channel it goes to — empty for the service's own — its text, the pull request it announces and the head commit its CI is read at, the moment it marks, and whom it tags. The zero Mentions tags no one.

func (Delivery) Made

func (d Delivery) Made() Announced

Made is the announcement the delivery makes: its pull request at its moment.

type DraftInput added in v0.4.0

type DraftInput struct {
	Subjects     []string
	IssueKey     jira.Key
	IssueSummary string
	IssueURL     string
	Template     string
	TitleSource  convention.TitleSource
}

DraftInput is what a pull request's title and body are proposed from: the branch's commit subjects, oldest first; the issue it names, with the summary and link the tracker gave — empty where it gave none; the template the body starts from, empty for none; and where the title comes from.

type Green added in v0.10.0

type Green struct {
	Pull forge.PullRequest
	Head string
}

Green is the pull request whose CI passed, and the head it passed at.

func AwaitGreen added in v0.10.0

func AwaitGreen(ctx context.Context, seams AwaitSeams, number int) (Green, error)

AwaitGreen reads the branch, its pull request and its CI at once, or once Delay has passed, and then every Interval, under ctx, until the CI of pull request number passes, answering the pull request as read then. It gives up with ErrNoCI, ErrPullChanged or ErrMergedFirst, or ends with ctx's error. A read that fails is told through Heard and waited past. It starts no goroutine: Pause is the caller's wait.

type GroupTag added in v0.5.0

type GroupTag struct {
	Slack      SlackTarget
	Checked    bool
	FromOwners bool
}

GroupTag is a user group an announcement offers: whether it starts checked, and whether it is offered because a team owning the changed paths is linked to it.

type Heard added in v0.10.0

type Heard struct {
	CI    forge.CI
	Armed Armed
	Err   error
}

Heard is one read of an armed announcement's CI: what it said and how the announcement stands on it, or why the read failed.

type IssueOrigin added in v0.5.0

type IssueOrigin int

IssueOrigin is where a branch's issue was found.

const (
	// OriginLink is an issue the branch was linked to by hand.
	OriginLink IssueOrigin = iota + 1
	// OriginBranch is an issue the branch's name names.
	OriginBranch
	// OriginPull is an issue the branch's pull request names in its title or
	// description.
	OriginPull
)

func BranchIssue added in v0.5.0

func BranchIssue(src IssueSource) (convention.IssueRef, IssueOrigin, bool)

BranchIssue is the issue a branch is for, and where that was found: the issue it was linked to by hand, which says so on purpose; otherwise the one its name names, as workflow names its own branches; otherwise the one its pull request names, for work begun outside workflow. A link that names no issue is passed over rather than trusted, since it was read back from disk.

type IssueSource added in v0.5.0

type IssueSource struct {
	Branch  string
	Link    string
	Pull    *forge.PullRequest
	Project string
}

IssueSource is everything that can say which issue a branch is for: the branch's name, the issue it was linked to by hand, and its pull request, when there is one. Project is the Jira project a key must be in, when one is configured.

type NeedsFieldsError added in v0.10.0

type NeedsFieldsError struct {
	Key  jira.Key
	Move jira.Transition
}

NeedsFieldsError is a move of Key that asks for fields, naming them.

func (NeedsFieldsError) Error added in v0.10.0

func (e NeedsFieldsError) Error() string

func (NeedsFieldsError) Unwrap added in v0.10.0

func (e NeedsFieldsError) Unwrap() error

Unwrap is ErrNeedsFields, the kind of refusal this is.

type NoTransitionError added in v0.10.0

type NoTransitionError struct {
	Key     jira.Key
	Status  string
	Offered []string
}

NoTransitionError is a move to Status the tracker does not offer Key from where it stands, naming the statuses it does offer.

func (NoTransitionError) Error added in v0.10.0

func (e NoTransitionError) Error() string

func (NoTransitionError) Unwrap added in v0.10.0

func (e NoTransitionError) Unwrap() error

Unwrap is ErrNoTransition, the kind of refusal this is.

type OwnerLink struct {
	Owner   string
	Team    bool
	OnSlack bool
	Slack   SlackTarget
}

OwnerLink is what was decided for one forge owner: whether they are a team or a person, as the forge told them apart when they were decided; whether they are on Slack; and if so, as whom — a user for a person, a user group for a team.

type OwnerSeams added in v0.5.0

type OwnerSeams struct {
	ChangedPaths func(ctx context.Context, base string) ([]string, error)
	CodeOwnersAt func(ctx context.Context, base string) (codeowners.File, bool, error)
	Author       func(ctx context.Context) (string, error)
	IsGroup      func(ctx context.Context, name string) (bool, error)
}

OwnerSeams are what the owners of a pull request's changes are read through: the paths the branch changes since base, the CODEOWNERS file base holds, who is opening the pull request, and whether a bare name is a group. A nil ChangedPaths or CodeOwnersAt means there are no owners to read; a nil Author leaves nobody out; a nil IsGroup takes every bare name for a person, as GitHub, where every team is org/team, does.

type OwnerState added in v0.5.0

type OwnerState int

OwnerState is what is known of a code owner on Slack.

const (
	// OwnerUnlinked is an owner never asked about, or whose link is not one an
	// owner of their kind can have.
	OwnerUnlinked OwnerState = iota
	// OwnerLinked is an owner known on Slack: a user as a user, a team as a
	// user group.
	OwnerLinked
	// OwnerNotOnSlack is an owner decided not to be on Slack, who is never
	// tagged and not asked again.
	OwnerNotOnSlack
)

type OwnerTag added in v0.5.0

type OwnerTag struct {
	Owner string
	Team  bool
	State OwnerState
	Slack SlackTarget
}

OwnerTag is one code owner as an announcement may tag them: a user, or a team (org/team, GitLab's group/subgroup), what is known of them on Slack, and whom they are there when linked.

type PullAlreadyOpenError

type PullAlreadyOpenError struct {
	Pull forge.PullRequest
}

PullAlreadyOpenError is ErrPullAlreadyOpen with the pull request that is open, so a surface can point at it.

func (PullAlreadyOpenError) Error

func (e PullAlreadyOpenError) Error() string

Error says what was refused, in words any surface can show.

func (PullAlreadyOpenError) Unwrap

func (PullAlreadyOpenError) Unwrap() error

Unwrap lets errors.Is match ErrPullAlreadyOpen.

type PullOptions

type PullOptions struct {
	Project     string
	TitleSource convention.TitleSource
	// Template is the template the body starts from, by name, as the
	// terminal's ctrl+t chooses one; empty starts from the first.
	Template string
	// Base is the branch the pull request merges into as a surface chose it,
	// by the name the forge knows; empty is the base the branch was read to
	// have left. The title, the body and the code owners are all read against
	// it.
	Base string
}

PullOptions are the configuration a pull request is composed under: the tracker's project, which picks the issue key out of the branch name, and where the title comes from.

type PullSeams

type PullSeams struct {
	Branch     func(ctx context.Context) (gitrepo.Branch, error)
	BranchOnto func(ctx context.Context, base string) (gitrepo.Branch, error)
	FindPull   func(ctx context.Context, branch string) (forge.PullRequest, bool, error)
	Templates  func() []forge.Template
	Issue      func(ctx context.Context, issueKey jira.Key) (jira.IssueDetail, error)
	BrowseURL  func(jira.Key) string
	Owners     OwnerSeams
}

PullSeams are what composing a pull request reads. A nil Branch or FindPull means there is nothing to open; a nil Templates, Issue or BrowseURL only leaves that part out of the proposal, as Owners left zero leaves out the reviewers. BranchOnto reads the branch against a base a surface chose, and is asked only when one was; nil then means there is nothing to open.

type PushFailedError

type PushFailedError struct {
	Output []string
}

PushFailedError is ErrPushFailed with what the push printed, which is where the reason — a rejected ref, a missing remote — is. Each line is neutralized, as Drain gives it: the remote writes its remote: lines, and every surface shows them.

func (PushFailedError) Error

func (e PushFailedError) Error() string

Error says the push failed, followed by its output.

func (PushFailedError) Unwrap

func (PushFailedError) Unwrap() error

Unwrap lets errors.Is match ErrPushFailed.

type RepositoryCommits added in v0.6.0

type RepositoryCommits struct {
	Repository string
	Commits    []gitrepo.DatedCommit
	Failed     error
}

RepositoryCommits is one repository's commits for the Summary, or why they could not be read. Repository names it when the Summary reads more than one, and is "" when it reads only the repository workflow is in.

type RepositoryError added in v0.6.0

type RepositoryError struct {
	Repository string
	Err        error
}

RepositoryError is why one repository of several could not be read, with the name it is listed by, so a surface that words the failure its own way can still say which repository it was.

func (RepositoryError) Error added in v0.6.0

func (f RepositoryError) Error() string

Error is the failure prefixed by the repository's name.

func (RepositoryError) Unwrap added in v0.6.0

func (f RepositoryError) Unwrap() error

Unwrap is the failure itself, so errors.Is still classifies it.

type RepositoryErrors added in v0.6.0

type RepositoryErrors []error

RepositoryErrors is why each repository of a read could not be read, one failure apiece. It is its own type, rather than errors.Join's, so Failures splits it and never an error that merely wraps two causes.

func (RepositoryErrors) Error added in v0.6.0

func (e RepositoryErrors) Error() string

Error is every failure, one to a line.

func (RepositoryErrors) Unwrap added in v0.6.0

func (e RepositoryErrors) Unwrap() []error

Unwrap is the failures, so errors.Is and errors.As still classify each.

type SlackTarget added in v0.5.0

type SlackTarget = messaging.SlackTarget

SlackTarget is a Slack user or user group, by its ID and the label it is shown by: messaging's own, by the name the loop's seams use.

type SourceRead added in v0.7.0

type SourceRead struct {
	Source activity.Source
	Read   func(ctx context.Context) activity.Read
}

SourceRead is one source's read for the Summary, not yet made, so a surface can make each on its own, under the context of whatever asked, and show each as it answers.

func SummaryReads added in v0.7.0

func SummaryReads(seams ActivitySeams, start, end time.Time) []SourceRead

SummaryReads are the reads of every source the seams reach, from start up to end, in the order the sources are listed: the one way every surface asks them, so a period reads the same in each.

type Tags added in v0.5.0

type Tags struct {
	Owners []OwnerTag
	Groups []GroupTag
}

Tags is who an announcement proposes to tag: the code owners, users then teams, and the user groups it offers.

func ProposeTags added in v0.5.0

func ProposeTags(
	owners codeowners.Owners, links []OwnerLink, repoGroups []SlackTarget, last []string, lastChosen bool,
	moment messaging.Moment,
) Tags

ProposeTags is who the announcement at moment proposes to tag. Only the ready-for-review announcement tags anyone; any other moment proposes no one. owners are the changed paths' owners, already without the author; links are what was decided for owners on this forge host; repoGroups are the user groups this repository may tag; and last is the groups chosen the last time, when lastChosen says there was a last time.

A bare name is a team when the forge now knows it as a group or it was decided as one, since a forge that could not be asked leaves it a person by its spelling; one decided as a person that the forge now knows as a group is asked about again, as a team.

The groups offered are the repository's, then any a linked owning team adds, and none whose ID is not a group's, which no post could tag. The groups linked to owning teams start checked, and so does the last choice; with no last choice, only the teams' groups do.

func (Tags) Mentions added in v0.5.0

func (t Tags) Mentions(checkedGroupIDs []string) (messaging.Mentions, error)

Mentions is the tags of the linked user owners and of checkedGroupIDs, the groups once each and in the order the tags offer them, whatever order they were checked in, so a post tags them as Proposed names them. A team owner is tagged through its group, when that group is checked. A group the tags did not offer is refused with ErrGroupNotOffered.

func (Tags) Proposed added in v0.9.0

func (t Tags) Proposed() (Tags, messaging.Mentions)

Proposed is the tags as a post sends them with no one's choice changed, and the mentions that post carries: the linked user owners, and the groups that start checked. An owner linked to an ID not shaped like its kind reads as unlinked, as ProposeTags reads such a link, and a group whose ID is not a group's starts unchecked, so the tags it answers name no one the mentions leave out, and a surface says of that owner that it was not tagged.

Jump to

Keyboard shortcuts

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