forge

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package forge applies what reaches a forge — comments, labels, issues, merge requests — on GitHub, GitLab, or a simulated forge for the tests. Every write is idempotent, so a run interrupted half-way can be resumed.

Index

Constants

View Source
const Missing = "" /* 128-byte string literal not displayed */

Missing says what to do when a write needs a forge and the project has none.

Variables

View Source
var ErrUnreachable = errors.New("forge unreachable")

ErrUnreachable marks a forge that did not answer: the run is blocked by something outside the role.

Functions

func Bodies added in v0.11.0

func Bodies(notes []Note) []string

Bodies is the text of each note, in order.

func InCI added in v0.2.2

func InCI() string

InCI names the variable that says the run is in CI, or "" outside it.

func KeepsBranches added in v0.2.2

func KeepsBranches(f Forge) bool

KeepsBranches says whether f's merge requests come from local branches: nothing is pushed to a remote, nor fetched from one.

func Marker

func Marker(key string) string

Marker is the hidden text that makes a comment findable again.

func TaskItems added in v0.12.0

func TaskItems(body string) map[string]bool

TaskItems maps each task list item of a body to whether it is ticked.

Types

type Backlog added in v0.5.0

type Backlog interface {
	// Issues lists the open issues, merge requests left out, by number,
	// each with the issues it waits on in the forge's own relation.
	Issues() ([]Issue, error)
	// AllIssues lists the issues open and closed, merge requests left out,
	// by number: a subject a role found is looked for in both (OpenOnce).
	AllIssues() ([]Issue, error)
	// Comments lists the comments on t, oldest first.
	Comments(t Target) ([]string, error)
	// Notes lists the comments on t with their authors, oldest first, as
	// Comments does.
	Notes(t Target) ([]Note, error)
	// Close closes an issue: as a duplicate of dup when dup > 0, else as
	// completed. Closing one already closed changes nothing.
	Close(id, dup int) error
	// Milestones lists the titles of the open milestones.
	Milestones() ([]string, error)
	// SetMilestone puts an issue in the open milestone with this title,
	// creating it when there is none.
	SetMilestone(id int, title string) error
	// SetBody rewrites an issue's body.
	SetBody(id int, body string) error
	// SetTitle renames an issue.
	SetTitle(id int, title string) error
	// AddSubIssue makes child a sub-issue of parent where the forge has
	// sub-issues, and says so; false on a forge that has none, the child
	// then listed in the parent's body by the caller. Adding one already
	// there changes nothing.
	AddSubIssue(parent, child int) (bool, error)
	// AddBlocker records that id waits on blocker in the forge's own
	// relation, and says so; false on a forge that has none — the caller
	// then writes a line in the body (ADR-0028). Adding one already there
	// changes nothing.
	AddBlocker(id, blocker int) (bool, error)
	// Closers lists what closed an issue, as the forge links it: the pull
	// or merge request, or the commit; nil when the forge does not say.
	Closers(id int) ([]Closer, error)
	// Ticks lists the boxes ticked and unticked in an issue's body, oldest
	// first, with who did each; nil when the forge does not say.
	Ticks(id int) ([]Tick, error)
	// Trail says when an open issue last got a label, and what pull or
	// merge requests and commits name it; an empty Labeled when the
	// forge does not say (ADR-0031).
	Trail(id int, label string) (Trail, error)
	// EnsureLabel creates a label when the project has none of that name,
	// so a person finds it in the forge's list to set.
	EnsureLabel(name, color, description string) error
}

Backlog is what a role acting on a project's issues needs from its forge (docs/spec/backlog-acts.md). Every forge workline speaks has it.

type Closer added in v0.14.0

type Closer struct {
	Kind string `json:"kind"`           // pull-request (a GitHub pull request, a GitLab merge request) or commit
	Ref  string `json:"ref"`            // how the forge names it: "#20", "!7", a short commit
	Text string `json:"text,omitempty"` // its title and description, or the commit's message
}

Closer is what closed an issue, as the forge links it: a pull or merge request merged, or a commit, with its text — the evidence a parent's report quotes (ADR-0029).

type Fake

type Fake struct{ Path string }

Fake is a forge kept in a JSON file, shared between processes, for the conformance tests. It can be told to fail its N-th write, once.

func (*Fake) AddBlocker added in v0.14.0

func (f *Fake) AddBlocker(id, blocker int) (bool, error)

func (*Fake) AddSubIssue added in v0.11.0

func (f *Fake) AddSubIssue(parent, child int) (bool, error)

func (*Fake) AllIssues added in v0.10.0

func (f *Fake) AllIssues() ([]Issue, error)

func (*Fake) Close added in v0.5.0

func (f *Fake) Close(id, dup int) error

func (*Fake) Closers added in v0.14.0

func (f *Fake) Closers(id int) ([]Closer, error)

func (*Fake) Comment

func (f *Fake) Comment(t Target, body, marker string) error

func (*Fake) Comments added in v0.5.0

func (f *Fake) Comments(t Target) ([]string, error)

func (*Fake) EnsureLabel added in v0.7.0

func (f *Fake) EnsureLabel(name, color, description string) error

func (*Fake) Issue

func (f *Fake) Issue(id int) (*Issue, error)

func (*Fake) Issues added in v0.5.0

func (f *Fake) Issues() ([]Issue, error)

func (*Fake) KeepIssue

func (f *Fake) KeepIssue(title, body string, create bool) (int, error)

func (*Fake) Label

func (f *Fake) Label(t Target, add, remove []string) error

func (*Fake) MergeRequest added in v0.4.0

func (f *Fake) MergeRequest(id int) (MergeRequest, error)

func (*Fake) Milestones added in v0.5.0

func (f *Fake) Milestones() ([]string, error)

func (*Fake) Notes added in v0.11.0

func (f *Fake) Notes(t Target) ([]Note, error)

func (*Fake) OpenIssue

func (f *Fake) OpenIssue(title, body, marker string) (int, error)

func (*Fake) OpenMergeRequest

func (f *Fake) OpenMergeRequest(branch, base, title, body string) (int, error)

func (*Fake) OpenMergeRequests

func (f *Fake) OpenMergeRequests(prefix string) ([]string, error)

func (*Fake) SetBody added in v0.6.0

func (f *Fake) SetBody(id int, body string) error

func (*Fake) SetMilestone added in v0.5.0

func (f *Fake) SetMilestone(id int, title string) error

func (*Fake) SetTitle added in v0.11.0

func (f *Fake) SetTitle(id int, title string) error

func (*Fake) Sticky

func (f *Fake) Sticky(t Target, body, marker string, create bool) error

func (*Fake) Ticks added in v0.12.0

func (f *Fake) Ticks(id int) ([]Tick, error)

func (*Fake) Trail added in v0.15.0

func (f *Fake) Trail(id int, label string) (Trail, error)

type FakeComment added in v0.11.0

type FakeComment Note

FakeComment is a comment: in the file, its body alone, or with its author as {body, author, insider, bot, created}.

func (FakeComment) MarshalJSON added in v0.11.0

func (c FakeComment) MarshalJSON() ([]byte, error)

func (*FakeComment) UnmarshalJSON added in v0.11.0

func (c *FakeComment) UnmarshalJSON(data []byte) error

type FakeItem

type FakeItem struct {
	ID        int           `json:"id"`
	Branch    string        `json:"branch,omitempty"` // a merge request's source branch
	Base      string        `json:"base,omitempty"`
	Closed    bool          `json:"closed,omitempty"`
	Reason    string        `json:"reason,omitempty"` // why it was closed: completed, not_planned or duplicate
	Milestone string        `json:"milestone,omitempty"`
	Fork      bool          `json:"fork,omitempty"` // a merge request from a fork
	Title     string        `json:"title,omitempty"`
	Body      string        `json:"body,omitempty"`
	Labels    []string      `json:"labels"`
	Comments  []FakeComment `json:"comments"`
	Author    string        `json:"author,omitempty"`
	Insider   bool          `json:"insider,omitempty"`
	Parent    int           `json:"parent,omitempty"`     // the issue it is a sub-issue of
	BlockedBy []int         `json:"blocked-by,omitempty"` // the issues it waits on, in the forge's own relation
	Ticks     []Tick        `json:"ticks,omitempty"`      // boxes ticked in its body, with who ticked them
	ClosedBy  []Closer      `json:"closed-by,omitempty"`  // what closed it: a pull request, a commit
	// Labeled says when it last got each label; Links, what names it
	// (Trail, ADR-0031).
	Labeled map[string]string `json:"labeled,omitempty"`
	Links   []Link            `json:"links,omitempty"`
}

FakeItem is an issue or a merge request.

type FakeState

type FakeState struct {
	Issues        []FakeItem `json:"issues"`
	MergeRequests []FakeItem `json:"merge-requests"`
	Milestones    []string   `json:"milestones,omitempty"`    // open milestones, by title
	Labels        []FakeItem `json:"labels,omitempty"`        // labels defined, their name as id
	SubIssues     bool       `json:"sub-issues,omitempty"`    // the forge has sub-issues, as GitHub
	Dependencies  bool       `json:"dependencies,omitempty"`  // the forge has a blocked-by relation, as GitHub
	FailOnWrite   int        `json:"fail-on-write,omitempty"` // the write that fails, counting from 1
	Writes        int        `json:"writes"`
}

FakeState is the file's content.

type Forge

type Forge interface {
	Issue(id int) (*Issue, error)
	// Comment posts body on t unless a comment carrying marker is already there.
	Comment(t Target, body, marker string) error
	// Sticky keeps one comment carrying marker on t, edited to body on each
	// run instead of a new one each time; with create false, it only edits
	// one already there.
	Sticky(t Target, body, marker string, create bool) error
	// Label adds and removes labels; adding one already there changes nothing.
	Label(t Target, add, remove []string) error
	// OpenIssue creates an issue, or comments on an open one with the same title.
	OpenIssue(title, body, marker string) (int, error)
	// KeepIssue rewrites the body of the open issue with this title, or opens
	// it when there is none and create is true: one issue, kept in place.
	KeepIssue(title, body string, create bool) (int, error)
	// OpenMergeRequest opens a merge request from branch into base, or
	// updates the title and body of the one already open from branch.
	OpenMergeRequest(branch, base, title, body string) (int, error)
	// OpenMergeRequests lists the branches of the open merge requests whose
	// branch starts with prefix, sorted.
	OpenMergeRequests(prefix string) ([]string, error)
	// MergeRequest says where a merge request comes from and goes.
	MergeRequest(id int) (MergeRequest, error)
}

Forge is what the engine needs from one.

func Open

func Open(spec, repo string) (Forge, error)

Open returns the forge named by spec: "github", "gitlab", "local" (kept in the clone), "cmd:<command>" (another forge, plugged by a command), "fake:<file>", or "" / "none" for no forge: what needs one is refused, and says so.

type Issue

type Issue struct {
	ID        int      `json:"id"`
	Title     string   `json:"title"`
	Body      string   `json:"body"`
	Labels    []string `json:"labels"`
	Closed    bool     `json:"closed,omitempty"`
	Reason    string   `json:"reason,omitempty"`    // why it was closed, when the forge says: completed, not_planned, duplicate
	Milestone string   `json:"milestone,omitempty"` // the title of the milestone it is in, if any
	Author    string   `json:"author,omitempty"`    // who opened it
	Insider   bool     `json:"insider,omitempty"`   // its author is a person of the project (GitHub: owner, member, collaborator; GitLab: Planner or above); false when the forge does not say
	// BlockedBy are the issues it waits on in the forge's own relation
	// (GitHub's dependencies, GitLab's is_blocked_by), open or closed, as
	// Issues gives them; a line in its body says the rest (ADR-0028).
	BlockedBy []int `json:"blocked-by,omitempty"`
	// Children are its sub-issues in the forge's own relation (GitHub's
	// sub-issues, GitLab's tasks), open or closed, as Issues gives them; a
	// task list under "## Sub-issues" in its body says the rest (ADR-0029).
	Children []int `json:"children,omitempty"`
}

Issue is what the line reads from a work item.

type Link struct {
	Kind string `json:"kind"`         // pull-request (a GitHub pull request, a GitLab merge request) or commit
	Ref  string `json:"ref"`          // how the forge names it: "#20", "!7", a short commit
	At   string `json:"at,omitempty"` // when it named the issue, RFC 3339 or YYYY-MM-DD
}

Link is a pull or merge request, or a commit, naming an issue.

type Local added in v0.2.2

type Local struct{ Repo string }

Local is a forge kept in the clone, never committed: for a project with no forge, or one whose person works alone. Each issue is a Markdown file, .git/workline/issues/<n>.md; each merge request is a local branch, recorded the same way in .git/workline/merge-requests/<n>.md. Nothing is pushed: `workline issues` reads them. Writes are idempotent, as on any forge.

func (*Local) AddBlocker added in v0.14.0

func (l *Local) AddBlocker(id, blocker int) (bool, error)

AddBlocker: the local forge has no relation between issues; the body says what an issue waits on (ADR-0028).

func (*Local) AddSubIssue added in v0.11.0

func (l *Local) AddSubIssue(parent, child int) (bool, error)

AddSubIssue: the local forge has no sub-issues; the parent's body lists its children.

func (*Local) AllIssues added in v0.10.0

func (l *Local) AllIssues() ([]Issue, error)

AllIssues: the local forge keeps no close reason.

func (*Local) Close added in v0.5.0

func (l *Local) Close(id, dup int) error

Close closes an issue; the local forge keeps no reason, the engine's comment says it.

func (*Local) Closers added in v0.14.0

func (l *Local) Closers(id int) ([]Closer, error)

Closers: the local forge does not link what closed an issue.

func (*Local) Comment added in v0.2.2

func (l *Local) Comment(t Target, body, marker string) error

func (*Local) Comments added in v0.5.0

func (l *Local) Comments(t Target) ([]string, error)

func (*Local) EnsureLabel added in v0.7.0

func (l *Local) EnsureLabel(name, color, description string) error

EnsureLabel: the local forge keeps no list of labels apart from its items.

func (*Local) Issue added in v0.2.2

func (l *Local) Issue(id int) (*Issue, error)

func (*Local) Issues added in v0.5.0

func (l *Local) Issues() ([]Issue, error)

func (*Local) Item added in v0.2.2

func (l *Local) Item(kind string, id int) (*LocalItem, error)

Item reads one issue or merge request.

func (*Local) Items added in v0.2.2

func (l *Local) Items(kind string) ([]*LocalItem, error)

Items lists the issues, or the merge requests, by number.

func (*Local) KeepIssue added in v0.2.2

func (l *Local) KeepIssue(title, body string, create bool) (int, error)

func (*Local) Label added in v0.2.2

func (l *Local) Label(t Target, add, remove []string) error

func (*Local) MergeRequest added in v0.4.0

func (l *Local) MergeRequest(id int) (MergeRequest, error)

func (*Local) Milestones added in v0.5.0

func (l *Local) Milestones() ([]string, error)

Milestones are the ones its open issues are in: the local forge keeps no milestone apart from them.

func (*Local) Notes added in v0.11.0

func (l *Local) Notes(t Target) ([]Note, error)

Notes: the local forge keeps no author; whoever writes in the clone is of the project, as its issues' authors are.

func (*Local) OpenIssue added in v0.2.2

func (l *Local) OpenIssue(title, body, marker string) (int, error)

func (*Local) OpenMergeRequest added in v0.2.2

func (l *Local) OpenMergeRequest(branch, base, title, body string) (int, error)

func (*Local) OpenMergeRequests added in v0.2.2

func (l *Local) OpenMergeRequests(prefix string) ([]string, error)

func (*Local) SetBody added in v0.6.0

func (l *Local) SetBody(id int, body string) error

func (*Local) SetMilestone added in v0.5.0

func (l *Local) SetMilestone(id int, title string) error

func (*Local) SetTitle added in v0.11.0

func (l *Local) SetTitle(id int, title string) error

func (*Local) StateOf added in v0.2.2

func (l *Local) StateOf(it *LocalItem) string

StateOf is an item's state as it stands: a merge request whose branch is gone is closed, one whose branch its base holds is merged — the person merges it with git.

func (*Local) Sticky added in v0.2.2

func (l *Local) Sticky(t Target, body, marker string, create bool) error

func (*Local) Ticks added in v0.12.0

func (l *Local) Ticks(id int) ([]Tick, error)

Ticks: the local forge keeps no history; a box ticked in the clone is a person of the project's, as its comments are.

func (*Local) Trail added in v0.15.0

func (l *Local) Trail(id int, label string) (Trail, error)

Trail: the local forge keeps no history, nor links: it does not say when an issue got a label (ADR-0031).

type LocalItem added in v0.2.2

type LocalItem struct {
	ID        int      `yaml:"-"`
	Title     string   `yaml:"title"`
	State     string   `yaml:"state"` // open or closed, as written; see Local.StateOf
	Labels    []string `yaml:"labels,flow"`
	Milestone string   `yaml:"milestone,omitempty"`
	Branch    string   `yaml:"branch,omitempty"` // a merge request's local branch
	Base      string   `yaml:"base,omitempty"`
	Body      string   `yaml:"-"`
	Comments  []string `yaml:"-"`
}

LocalItem is an issue or a merge request of the local forge.

type MergeRequest added in v0.4.0

type MergeRequest struct {
	Branch string // the branch it comes from
	Base   string // the branch it goes into; "" when the forge does not say
	Here   bool   // the branch lives in this repository, not in a fork
}

MergeRequest is where a merge request comes from and where it goes.

type Note added in v0.11.0

type Note struct {
	Body    string `json:"body"`
	Author  string `json:"author,omitempty"`  // who wrote it; "" when the forge does not say
	Insider bool   `json:"insider,omitempty"` // its author is a person of the project, as Issue.Insider
	Bot     bool   `json:"bot,omitempty"`     // its author is a bot: a token's user, an app
	Created string `json:"created,omitempty"` // when it was written, RFC 3339 or YYYY-MM-DD; "" when the forge does not say
}

Note is a comment with who wrote it: what a reply decides counts only from the right people (ADR-0021).

type Target

type Target struct {
	Kind string `json:"kind" yaml:"kind"` // "issue" or "merge-request"
	ID   int    `json:"id" yaml:"id"`
}

Target is what a comment or a label goes on.

func (Target) String

func (t Target) String() string

type Tick added in v0.12.0

type Tick struct {
	Item string `json:"item"`           // the item's text, as the forge gives it: its hidden markers' text in it
	Done bool   `json:"done,omitempty"` // ticked; false: unticked
	Note        // its author: Author, Insider, Bot; no author and not Insider when the forge does not say
}

Tick is a box of a task list ticked, or unticked, in an issue's body, with who did it: a person's yes in a report (ADR-0025).

func TicksBetween added in v0.12.0

func TicksBetween(before, after string, who Note) []Tick

TicksBetween are the boxes a version of a body ticked or unticked from the one before ("" for none), each given to who wrote that version.

type Trail added in v0.15.0

type Trail struct {
	Labeled string `json:"labeled,omitempty"` // when it last got the label, RFC 3339 or YYYY-MM-DD; "" when the forge does not say
	Links   []Link `json:"links,omitempty"`
}

Trail is what a forge says of an open issue since it got a label: when it last got it, and the pull or merge requests and commits that name the issue, each with when — what started it, or nothing (ADR-0031).

Jump to

Keyboard shortcuts

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