Documentation
¶
Overview ¶
Package forge defines gu's provider-neutral view of a code-hosting service. Views depend on this package alone; they never import a provider package.
Index ¶
- Constants
- Variables
- func ParseTarget(target string) (kind string, id int, err error)
- func SplitPath(full string) (namespace, project string)
- func SuggestionBody(s Suggestion, style SuggestionStyle) string
- type Approvals
- type Caps
- type DiffComment
- type DiffRefs
- type Epic
- type FieldPatch
- type Forge
- type Issue
- type IssueFilter
- type Job
- type MRFilter
- type Membership
- type MergeRequest
- type Milestone
- type MyWorkFilter
- type Namespace
- type Note
- type Pipeline
- type PipelineFilter
- type Ref
- type Repo
- type Review
- type ReviewResult
- type Role
- type Scope
- type Side
- type Status
- type Suggestion
- type SuggestionStyle
- type Thread
- type TreeEntry
- type User
- type Verdict
- type WorkItem
Constants ¶
const ( StatusCategoryWaiting = "waiting" StatusCategoryRunning = "running" StatusCategorySuccess = "success" StatusCategoryCancelled = "cancelled" )
The status categories a work-item status can fall into — see Status.Category.
const ( StatusSuccess = "success" StatusFailed = "failed" StatusRunning = "running" StatusPending = "pending" StatusCanceled = "canceled" StatusSkipped = "skipped" StatusManual = "manual" )
Pipeline statuses, normalised across providers.
const ( KindIssue = "issue" KindMR = "mr" )
Kinds of work item.
const ( EntryDir = "dir" EntryFile = "file" )
Entry kinds in a repository tree.
Variables ¶
var ErrNotPositioned = errors.New("this comment cannot be anchored to the diff")
ErrNotPositioned is returned when a comment cannot be anchored to the diff — the merge request's refs are unknown, or the provider refused the position.
It is a distinct error rather than a silent fallback because a comment that was meant to sit on line 42 and instead appears at the bottom of the discussion is a different comment, and the author should be told which they got.
var ErrReadOnly = errors.New("read-only mode: relaunch without --read-only to make changes")
ErrReadOnly is returned by every mutating method when gu runs read-only.
Functions ¶
func ParseTarget ¶
ParseTarget splits an address — "issue/12" or "mr/42" — into its kind and number.
The format is this package's, named in Forge's own documentation, so both providers parse it through here rather than each keeping its own copy of the same three lines.
func SuggestionBody ¶
func SuggestionBody(s Suggestion, style SuggestionStyle) string
SuggestionBody renders a suggestion as the comment body a provider expects.
The fence is four backticks because a suggestion routinely contains code with three, and a fence the content can close is a suggestion that arrives truncated.
Types ¶
type Approvals ¶ added in v0.7.0
type Approvals struct {
Required int
Left int
By []string
// IHave and ICan are about the authenticated user: whether this account has
// approved, and whether it is allowed to. Both providers refuse an author
// approving their own work, so ICan is what decides between offering the
// action and explaining why it is absent.
IHave bool
ICan bool
}
Approvals is where a merge request stands.
func (Approvals) Satisfied ¶ added in v0.7.0
Satisfied reports whether the merge request has the approvals it is required to have.
A project that requires none is never "satisfied" in this sense, which is not the same as being unapproved: on GitLab's free tier Required is zero and people approve anyway, so a caller drawing this has to look at By as well.
type Caps ¶
type Caps struct {
Suggestions bool
MultiLineSuggestions bool
SuggestionStyle SuggestionStyle
}
Caps says what a provider will accept, so the UI can offer only what will work rather than posting something that comes back rejected.
type DiffComment ¶
type DiffComment struct {
Body string
OldPath, NewPath string
Side Side
Line, StartLine int
Refs DiffRefs
}
DiffComment is a review comment anchored to a place in the diff.
Line is the last line of the range and StartLine the first, zero for a single line — the shape both providers use, and the shape a multi-line suggestion needs.
type DiffRefs ¶
type DiffRefs struct {
BaseSHA, HeadSHA, StartSHA string
}
DiffRefs are the commits a merge request's diff is computed from.
GitLab wants all three to position a comment; GitHub positions against the head commit alone. They travel together because they come from one place and are useless apart.
func (DiffRefs) Positioned ¶
Positioned reports whether the refs will carry a comment's position. The head commit is the one both providers need.
type Epic ¶ added in v0.6.0
Epic is one epic. Epics are a group-level, licensed GitLab feature, so an empty list is the ordinary answer rather than a fault.
type FieldPatch ¶
type FieldPatch struct {
State *string
// Labels and Assignees are the issue's complete new set: a nil slice
// leaves them alone, a non-nil empty slice clears them.
Labels []string
Assignees []string
// Milestone is the milestone title; GitLab resolves it to an id before
// writing.
Milestone *string
// Status is the work-item status name — GitLab's To do / In progress /
// Done — set over GraphQL, since the REST issues endpoint has no field
// for it. GitLab only.
Status *string
// Epic is the epic's title. GitLab only, group-level, licensed, and set
// over GraphQL through the parent widget rather than a REST field.
Epic *string
}
FieldPatch is a partial update. A nil field means "leave unchanged", which is why every field is a pointer or a nilable slice: a nil pointer, or a nil slice, leaves that field alone, and (Milestone and Epic only) a pointer to "" clears it. Status is the exception — GitLab has no "no status" a work item can be reset to, so a pointer to "" is refused rather than silently dropped; see gitlab.Client.SetFields.
type Forge ¶
type Forge interface {
// Capabilities reports what this provider will accept, so the UI can offer
// only what will work rather than posting something that comes back
// rejected.
Capabilities() Caps
// Whoami identifies the authenticated user.
Whoami(ctx context.Context) (User, error)
// Namespaces lists children of parent; parent "" lists top-level ones.
Namespaces(ctx context.Context, parent string) ([]Namespace, error)
// Repos lists projects directly inside namespace.
Repos(ctx context.Context, namespace string) ([]Repo, error)
// Issues lists issues in a project.
Issues(ctx context.Context, ref Ref, f IssueFilter) ([]Issue, error)
// Issue fetches one issue by its number.
Issue(ctx context.Context, ref Ref, id int) (Issue, error)
// GroupIssues lists issues across every project in a group, which is what
// a group-level board shows.
GroupIssues(ctx context.Context, group string, f IssueFilter) ([]Issue, error)
// Labels, Milestones and Members are the values a board axis can take in a
// project or a group — what a board needs to draw a lane before anything
// is in it, and the name-to-id lookup a write needs to put something there.
Labels(ctx context.Context, s Scope) ([]string, error)
Milestones(ctx context.Context, s Scope) ([]Milestone, error)
Members(ctx context.Context, s Scope) ([]User, error)
// Statuses and Epics are the other two axes a board can group by. Both are
// GitLab-only and both may be absent — an older instance, an edition
// without epics — in which case they answer with nothing and no error, and
// the axis simply does not appear. A transport or GraphQL failure
// degrades exactly the same way, on purpose: a board legend that cannot
// list statuses must still open.
Statuses(ctx context.Context, s Scope) ([]Status, error)
Epics(ctx context.Context, s Scope) ([]Epic, error)
// StatusesStrict and EpicsStrict are Statuses and Epics' own listings, but
// propagating a fetch failure instead of folding it into an empty list.
// The status and epic pickers (internal/ui/board.go's pickStatus and
// pickEpic) need the two told apart: their only way to say "nothing to
// choose" is an empty list, so a transient failure answered the same way
// would look exactly like a project with none — one enter away from
// setting a field to something nobody chose. The board legend has no such
// ambiguity to protect against and keeps calling Statuses/Epics.
StatusesStrict(ctx context.Context, s Scope) ([]Status, error)
EpicsStrict(ctx context.Context, s Scope) ([]Epic, error)
// MergeRequests lists merge requests or pull requests in a project.
MergeRequests(ctx context.Context, ref Ref, f MRFilter) ([]MergeRequest, error)
// MergeRequest fetches one merge request by its number.
MergeRequest(ctx context.Context, ref Ref, mr int) (MergeRequest, error)
// MergeRequestDiff returns a merge request as a unified diff.
MergeRequestDiff(ctx context.Context, ref Ref, mr int) (string, error)
// Threads lists the discussions on an issue or merge request. target is
// "issue/<id>" or "mr/<id>".
Threads(ctx context.Context, ref Ref, target string) ([]Thread, error)
// Tree lists the entries directly under a path in a repository. path ""
// is the root; ref "" is the default branch.
Tree(ctx context.Context, ref Ref, path, rev string) ([]TreeEntry, error)
// FileContent returns a file's bytes.
FileContent(ctx context.Context, ref Ref, path, rev string) ([]byte, error)
// MyWork lists the current user's issues and merge requests across every
// project they can see, in whichever roles the filter asks for.
MyWork(ctx context.Context, f MyWorkFilter) ([]WorkItem, error)
// Starred lists the current user's starred projects.
Starred(ctx context.Context) ([]Repo, error)
// Pipelines lists CI runs in a project.
Pipelines(ctx context.Context, ref Ref, f PipelineFilter) ([]Pipeline, error)
// Jobs lists the steps of one pipeline.
Jobs(ctx context.Context, ref Ref, pipelineID int) ([]Job, error)
// JobLog returns a job's log. The caller closes it.
JobLog(ctx context.Context, ref Ref, jobID int) (io.ReadCloser, error)
// Comment posts a note. target is "issue/<id>" or "mr/<id>".
Comment(ctx context.Context, ref Ref, target, body string) error
// CreateThread starts a new discussion, rather than replying to the main
// one. Review comments want their own thread.
CreateThread(ctx context.Context, ref Ref, target, body string) error
// CreateDiffThread starts a discussion anchored to a place in the diff,
// which is where a review comment belongs and the only place a suggestion
// can be posted. It returns ErrNotPositioned when the anchor cannot be
// honoured, so the caller can fall back to an ordinary thread and say so.
CreateDiffThread(ctx context.Context, ref Ref, mr int, c DiffComment) error
// SetFields applies a partial update to an issue or merge request.
SetFields(ctx context.Context, ref Ref, target string, patch FieldPatch) error
// RetryPipeline re-runs a pipeline's failed and canceled jobs.
RetryPipeline(ctx context.Context, ref Ref, pipelineID int) error
// Merge merges a merge request.
Merge(ctx context.Context, ref Ref, mr int) error
// Rebase brings a merge request up to date with its target branch,
// through the provider's API rather than any local git.
Rebase(ctx context.Context, ref Ref, mr int) error
// Approvals reports who has approved a merge request and what it still
// needs.
Approvals(ctx context.Context, ref Ref, mr int) (Approvals, error)
// Approve records the authenticated user's approval.
Approve(ctx context.Context, ref Ref, mr int) error
// Unapprove revokes it.
Unapprove(ctx context.Context, ref Ref, mr int) error
// SubmitReview publishes a whole review — the inline remarks, an optional
// summary and a verdict — as one unit, so the author receives one
// notification rather than one per remark.
SubmitReview(ctx context.Context, ref Ref, mr int, r Review) (ReviewResult, error)
}
Forge is the provider-neutral API surface. This iteration implements the read methods plus Comment and SetFields; pipelines, merging and rebasing arrive with later slices and will extend this interface.
func Multi ¶
Multi aggregates the given forges, keyed by profile name. With fewer than two it returns the single forge unchanged rather than paying for the fan-out.
func ReadOnly ¶
ReadOnly returns f wrapped so that every mutating method fails with ErrReadOnly. The guard lives here rather than in the UI so that a view added later cannot bypass it by forgetting a check — this is the fail-closed placement. Read methods are promoted from the embedded Forge and pass through untouched.
type Issue ¶
type Issue struct {
// Ref names the project the issue belongs to. It matters for listings
// that span projects, where a card has to say where it came from.
Ref Ref
ID int
Title string
Description string
State string // "opened" | "closed"
// Status is the provider work-item status, empty when it has none.
Status string
Author string
Assignees []string
Labels []string
// LabelColors maps a label to the colour the provider gave it, for the
// places a label is shown rather than matched on. Empty where a provider
// has no colours or was not asked for them — a label without one is drawn
// plain, never dropped.
LabelColors map[string]string
Milestone string
// The rest is what a provider's own sidebar shows. Anything a provider
// does not have stays zero rather than being faked.
Epic string
Weight int
DueDate string
Upvotes int
Downvotes int
Comments int
UpdatedAt time.Time
WebURL string
Raw json.RawMessage
}
Issue is an issue on either provider.
type IssueFilter ¶
type IssueFilter struct {
State string // "" means all
Labels []string
Search string
// WithStatus asks for the provider work-item status (GitLab's To do /
// In progress / Done). It costs an extra request, so only the board —
// which groups by it — sets it.
WithStatus bool
}
IssueFilter narrows an issue listing.
type Job ¶
type Job struct {
ID int
Name string
Stage string
Status string
AllowFailure bool
Duration time.Duration
FinishedAt time.Time
WebURL string
Raw json.RawMessage
}
Job is one step of a pipeline.
type Membership ¶
type Membership string
Membership is how a note's author relates to the project, as the provider reports it. It is empty when the provider does not say, which is not the same as having no standing.
const ( MemberOwner Membership = "owner" MemberMaintainer Membership = "maintainer" MemberDeveloper Membership = "developer" MemberReporter Membership = "reporter" MemberMember Membership = "member" MemberContributor Membership = "contributor" )
The standings both providers can be mapped onto. GitLab reports an access level; GitHub an author association.
type MergeRequest ¶
type MergeRequest struct {
ID int
Title string
Description string
State string // "opened" | "merged" | "closed"
Author string
SourceBranch string
TargetBranch string
Draft bool
Labels []string
Assignees []string
Reviewers []string
Milestone string
Mergeable bool
Conflicts bool
Upvotes int
Downvotes int
Comments int
UpdatedAt time.Time
WebURL string
// Refs are the commits the diff is taken against. A review comment has to
// name them to be positioned, so without them a comment can only be posted
// unanchored — and a suggestion cannot be posted at all.
Refs DiffRefs
Raw json.RawMessage
}
MergeRequest models a GitLab merge request and a GitHub pull request as one type. Collapsing them is what makes cross-provider stacks possible.
type Milestone ¶ added in v0.6.0
Milestone is one milestone, with the id a write needs alongside the title a lane is named by.
type MyWorkFilter ¶
MyWorkFilter selects which of the current user's work to list. Empty slices mean "all of them", so the zero filter is every kind in every role.
func (MyWorkFilter) WantsKind ¶
func (f MyWorkFilter) WantsKind(kind string) bool
WantsKind reports whether the filter covers a kind of work item.
func (MyWorkFilter) WantsRole ¶
func (f MyWorkFilter) WantsRole(role Role) bool
WantsRole reports whether the filter covers a role.
type Note ¶
type Note struct {
ID int
Author string
// AuthorName is the display name, empty when the provider gives only a
// login. It is what an avatar takes its initials from.
AuthorName string
AvatarURL string
Membership Membership
Body string
CreatedAt time.Time
// System marks a note the provider generated — "changed the milestone",
// "added 1 commit" — rather than something a person wrote. These are the
// history a detail page draws as a timeline.
System bool
Resolved bool
}
Note is one comment on an issue or merge request, or one event in its history.
type Pipeline ¶
type Pipeline struct {
ID int
Status string
Ref string // the branch or tag it ran on
SHA string
Source string
WebURL string
CreatedAt time.Time
UpdatedAt time.Time
Raw json.RawMessage
}
Pipeline is one CI run: a GitLab pipeline or a GitHub Actions workflow run.
type PipelineFilter ¶
PipelineFilter narrows a pipeline listing.
type Ref ¶
type Ref struct {
Profile string
Host string
Namespace string // "atomic-blend/backend"
Project string // "auth"
}
Ref is the universal identity of a project. Profile is part of the key because two profiles routinely share a host: gitlab.com serves both the personal and the work account, distinguished only by CLI config directory.
type Repo ¶
type Repo struct {
Ref Ref
Name string
Description string
LastActivity time.Time
Starred bool
WebURL string
}
Repo is a GitLab project or a GitHub repository.
type Review ¶ added in v0.7.0
type Review struct {
Verdict Verdict
Body string // the summary note; may be empty
Comments []DiffComment // the inline remarks, in reading order
}
Review is a pass over a diff submitted as one unit: the inline remarks, an optional summary, and the standing they add up to.
It is one type rather than a call per remark because that is what both providers model, and because one review should reach the author as one notification rather than as one per line commented on.
type ReviewResult ¶ added in v0.7.0
type ReviewResult struct{ Unanchored int }
ReviewResult says what became of a review the provider accepted.
Unanchored counts the remarks it would not position, which went into the review's summary instead. A remark meant for line 42 that arrives at the foot of the discussion is a different remark, so the count comes back rather than being swallowed.
type Role ¶
type Role string
Role is how the current user relates to a work item. A single item can match more than one; the first matching role in the query order wins.
type Scope ¶ added in v0.6.0
Scope is what a listing covers: one project, or a whole group. It is one type rather than two methods because everything a board asks for — its labels, its milestones, its members — is asked the same way of either.
type Status ¶ added in v0.6.0
type Status struct {
ID string
Name string
// Category buckets the status the way GitLab's own board orders its
// columns — waiting, running, success or cancelled — derived from the
// status's icon rather than its name or its always-0 position, which is
// why internal/ui/board.go sorts the status lanes by this and not by
// Name. One of the StatusCategory* constants; a provider that answers
// with something else (or nothing) is treated as StatusCategoryRunning
// wherever this is read.
Category string
}
Status is a work-item status, with the global id a mutation addresses it by.
type Suggestion ¶
type Suggestion struct {
// Replacement is the new text, one entry per line, without newlines.
Replacement []string
Above int
Below int
// Note is the reviewer's own words, put above the block.
Note string
}
Suggestion is a proposed replacement for a span of lines, which the author can apply from the web UI with one click.
Above and Below say how many lines either side of the anchor the block replaces. GitLab expresses a multi-line suggestion that way; GitHub does it with the comment's own line range. The neutral form carries both so neither provider's shape leaks into the caller.
type SuggestionStyle ¶
type SuggestionStyle int
SuggestionStyle is how a provider spells a multi-line suggestion.
const ( // GitHubSuggestions carry no span: the comment's line range says how much // is replaced. GitHubSuggestions SuggestionStyle = iota // GitLabSuggestions carry the span in the fence itself. GitLabSuggestions )
The two spellings.
type Thread ¶
type Thread struct {
ID string
// Resolvable threads are review conversations; a plain comment is not.
Resolvable bool
Resolved bool
Notes []Note
}
Thread is a discussion: a single comment, or a resolvable conversation.
func (Thread) LastActivity ¶
LastActivity is when the thread was most recently added to.
type Verdict ¶ added in v0.7.0
type Verdict string
Verdict is the standing a reviewer takes. Comment is a review with no standing: remarks, but neither a blessing nor a block.
const ( VerdictComment Verdict = "comment" VerdictApprove Verdict = "approve" VerdictRequestChanges Verdict = "request_changes" )
The three conclusions a review can reach. Both providers support all three — GitLab through a draft-note batch published with a reviewer state, GitHub through a review event — so none of them needs a capability to gate it.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package github implements forge.Forge against the GitHub REST API.
|
Package github implements forge.Forge against the GitHub REST API. |
|
Package gitlab implements forge.Forge against the GitLab REST API.
|
Package gitlab implements forge.Forge against the GitLab REST API. |