forge

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

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

View Source
const (
	StatusCategoryWaiting   = "waiting"
	StatusCategoryRunning   = "running"
	StatusCategorySuccess   = "success"
	StatusCategoryCancelled = "cancelled"
)

The status categories a work-item status can fall into — see Status.Category.

View Source
const (
	StatusSuccess  = "success"
	StatusFailed   = "failed"
	StatusRunning  = "running"
	StatusPending  = "pending"
	StatusCanceled = "canceled"
	StatusSkipped  = "skipped"
	StatusManual   = "manual"
)

Pipeline statuses, normalised across providers.

View Source
const (
	KindIssue = "issue"
	KindMR    = "mr"
	// KindPipeline is a todo with no issue or merge request behind it — a
	// failed build. It opens the project's pipelines, there being no page of
	// its own to open.
	KindPipeline = "pipeline"
)

Kinds of work item.

View Source
const (
	EntryDir  = "dir"
	EntryFile = "file"
)

Entry kinds in a repository tree.

Variables

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

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

View Source
var ErrTodoScope = errors.New("the token cannot read notifications: re-authenticate with the notifications scope")

ErrTodoScope reports a token that is not allowed to read the provider's todo list — a GitHub token without the notifications scope.

It is distinct from an empty list on purpose: "nothing is waiting on you" and "I was not allowed to look" are opposite answers, and an inbox that draws them the same way lies about the one thing it exists to report.

Functions

func ParseTarget

func ParseTarget(target string) (kind string, id int, err error)

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 SplitPath

func SplitPath(full string) (namespace, project string)

SplitPath breaks "group/subgroup/project" into its namespace and project.

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

func (a Approvals) Satisfied() bool

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

func (r DiffRefs) Positioned() bool

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

type Epic struct {
	ID    string
	Title string
}

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)
	// Todos lists what is waiting on the authenticated user: review requests,
	// mentions, assignments, failed builds. It is the inbox half of the tool,
	// where MyWork is the browse half.
	Todos(ctx context.Context, f TodoFilter) ([]Todo, error)
	// ResolveTodo marks one todo done. It takes the whole Todo rather than an
	// id because an id on its own names no account to send the write to.
	ResolveTodo(ctx context.Context, t Todo) 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

func Multi(byProfile map[string]Forge) Forge

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

func ReadOnly(f Forge) Forge

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 MRFilter

type MRFilter struct {
	State  string
	Labels []string
	Search string
}

MRFilter narrows a merge-request listing.

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

type Milestone struct {
	ID    int
	Title string
}

Milestone is one milestone, with the id a write needs alongside the title a lane is named by.

type MyWorkFilter

type MyWorkFilter struct {
	Kinds []string
	Roles []Role
	State string
}

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 Namespace

type Namespace struct {
	ID       int
	FullPath string
	Name     string
	Parent   string
	WebURL   string
}

Namespace is a GitLab group or subgroup, or a GitHub organisation.

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.

func (Note) Who

func (n Note) Who() string

Who names the author the way a person would recognise them.

type PartialError added in v0.11.0

type PartialError struct {
	Profiles []string
	Err      error
}

PartialError reports the accounts that could not be read when others could.

It exists because gather's "any success wins" is the wrong answer for a list whose whole job is completeness: a silently short inbox is indistinguishable from a quiet morning, and the two call for opposite reactions.

func (*PartialError) Error added in v0.11.0

func (e *PartialError) Error() string

func (*PartialError) Unwrap added in v0.11.0

func (e *PartialError) Unwrap() error

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.

func (Pipeline) Finished

func (p Pipeline) Finished() bool

Finished reports whether the run has stopped, whatever its outcome.

func (Pipeline) Succeeded

func (p Pipeline) Succeeded() bool

Succeeded reports whether the run finished green.

type PipelineFilter

type PipelineFilter struct {
	Ref    string // only runs on this branch
	Status string
}

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.

func (Ref) Path

func (r Ref) Path() string

Path returns the "namespace/project" form used by both providers' APIs.

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.

const (
	RoleAssigned Role = "assigned"
	RoleAuthored Role = "authored"
	RoleReviewer Role = "reviewer"
)

The roles a user can have on an issue or merge request.

type Scope added in v0.6.0

type Scope struct {
	Ref   Ref
	Group string
}

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.

func (Scope) IsGroup added in v0.6.0

func (s Scope) IsGroup() bool

IsGroup reports whether the scope is a group rather than a single project.

func (Scope) Path added in v0.6.0

func (s Scope) Path() string

Path is what a provider addresses the scope by.

type Side

type Side string

Side names which version of a file a comment is about.

const (
	NewSide Side = "new"
	OldSide Side = "old"
)

The two sides of a diff.

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

func (t Thread) LastActivity() time.Time

LastActivity is when the thread was most recently added to.

func (Thread) Opened

func (t Thread) Opened() time.Time

Opened is when the thread was started.

type Todo added in v0.11.0

type Todo struct {
	// Ref carries the profile, which is what routes a resolve back to the
	// account that owns it — an id on its own names no account.
	Ref Ref
	ID  string // GitLab's ints and GitHub's thread ids, both as strings
	// Reason is the shared standing; ReasonRaw is the provider's own word, kept
	// so a reason gu has never heard of is still reportable rather than hidden.
	// The same pairing as Membership.
	Reason    TodoReason
	ReasonRaw string
	Kind      string // KindIssue | KindMR | KindPipeline
	Target    int    // issue or merge request number; 0 for a pipeline
	Title     string
	Author    string
	UpdatedAt time.Time
	WebURL    string
}

Todo is one thing waiting on the authenticated user.

type TodoFilter added in v0.11.0

type TodoFilter struct{ State string }

TodoFilter selects which todos to list. An empty State means pending, which is the only one the inbox asks for.

type TodoReason added in v0.11.0

type TodoReason string

TodoReason is why something is waiting on you, mapped onto the standings both providers can express.

const (
	TodoReviewRequested TodoReason = "review requested"
	TodoMentioned       TodoReason = "mentioned"
	TodoAssigned        TodoReason = "assigned"
	TodoBuildFailed     TodoReason = "build failed"
	TodoUnresolved      TodoReason = "unresolved thread"
	TodoOther           TodoReason = "other"
)

The reasons a todo can exist. GitLab's action_name and GitHub's notification reason both map onto these.

type TreeEntry

type TreeEntry struct {
	Name string
	Path string
	Kind string // EntryDir | EntryFile
}

TreeEntry is one item in a repository tree: a directory or a file.

func (TreeEntry) IsDir

func (e TreeEntry) IsDir() bool

IsDir reports whether the entry can be descended into.

type User

type User struct {
	ID       int
	Username string
	Name     string
}

User is an account on a host.

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.

type WorkItem

type WorkItem struct {
	Ref       Ref
	Kind      string // KindIssue | KindMR
	Role      Role
	ID        int
	Title     string
	State     string
	UpdatedAt time.Time
	WebURL    string
}

WorkItem is a cross-project entry in the "my work" views.

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.

Jump to

Keyboard shortcuts

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