Documentation
¶
Overview ¶
Package epic holds the features a board is built out of: a description for a person and the tickets that make it up, kept on a ref of its own, refs/jaira/epics/<name>, and nowhere else.
An epic is not a ticket. It has no definition of done, runs through no lane and is worked by no agent — it is a description plus a list. Nor is it a milestone: a milestone is a round of work, and what is unfinished moves on to the next one; an epic is a feature, and what is unfinished stays in it.
The member list is in the epic, never on the tickets. Sorting a ticket into an epic therefore writes the epic's ref alone, and never the ref of a ticket an agent may be working at that very moment — so the two cannot conflict.
There is no working file. The ref is read directly, and an edit that has not reached the remote yet is read back out of the outbox, which is the only copy of it. Both are behind the Refs interface, so nothing in here talks to git.
The file the ref carries is read and written the way a milestone file is: frontmatter for the facts, one "- <ticket id>" line per member below it, and every other line kept verbatim across a write, so a person may edit it by hand and read it in a diff.
Index ¶
- Variables
- func Covered(members []string, ix *link.Index) []string
- func Merge(name string, base, ours, theirs []byte) []byte
- func NormalizeName(raw string) (string, bool, error)
- func Unassigned(epics []*Epic, ix *link.Index, board []*ticket.Ticket) []*ticket.Ticket
- type Epic
- func Assign(s *ticket.Store, r Refs, name string, ids []string, add bool) (*Epic, []string, error)
- func Create(s *ticket.Store, r Refs, name, title, description string, ids []string) (*Epic, error)
- func FromBytes(name string, content []byte) *Epic
- func Load(r Refs, name string) (*Epic, error)
- func LoadAll(r Refs) ([]*Epic, error)
- func New(name, title, description string, now time.Time) *Epic
- func Set(s *ticket.Store, r Refs, name string, p Patch) (*Epic, bool, error)
- type Fields
- type Patch
- type Progress
- type Refs
- type Ticket
Constants ¶
This section is empty.
Variables ¶
var ErrChanged = errors.New("epic changed since it was read")
ErrChanged means the epic was changed since the editor read it, in a field the editor meant to change. Nothing was written.
var ErrExists = errors.New("epic exists already")
ErrExists means an epic of that name exists already.
var ErrNoRefs = errors.New("epics live on git refs, and this board has no repository with a remote")
ErrNoRefs means the board has nowhere to keep an epic: no repository or no remote. Epics live on refs only, so there is no local fallback to offer.
var ErrNoTitle = errors.New("an epic's title cannot be empty")
ErrNoTitle means an edit would leave the epic without a title; the name is not one, it is where the epic is kept.
var ErrNotFound = errors.New("no such epic")
ErrNotFound means no epic of that name exists.
Functions ¶
func Covered ¶
Covered expands a set of member ids by every ticket that follows on from one of them, transitively, members first in their order, follow-ups after in id order.
func Merge ¶
Merge rebases an edit that lost the race for the ref. base is what the edit started from, ours the edit, theirs what the remote holds now. The result is theirs with our member additions and removals applied, and our title, description and text where we changed them — so two people sorting tickets into one epic at once both get their tickets in, and nobody's hand edit is lost.
Where both sides changed the same field to different values, ours is kept and theirs goes below the text in a marked block, for a person to merge by hand: a person's text has no right answer a machine can pick, and dropping either side would lose what somebody wrote without anybody knowing.
func NormalizeName ¶
NormalizeName turns a written epic name into the form it is stored under: lowercase kebab, the tag rules. The name is a ref path segment and the name of the file inside the ref, so it has to be safe in both.
func Unassigned ¶
Unassigned returns the open tickets of a board that belong to no epic, neither by a member line nor by following on from a member. board is the listing the caller already has; tickets in a terminal lane are left out, since finished work needs no home.
Types ¶
type Epic ¶
type Epic struct {
Name string
Title string
Description string
CreatedAt time.Time
// contains filtered or unexported fields
}
Epic is one epic as it was read: every line kept verbatim beside the facts parsed out of them.
func Assign ¶
Assign puts tickets into an epic (add) or takes them out, in one write of the epic's ref and of nothing else. It returns the epic as written and the ids that actually changed; nothing is written when none did. ids must be full ticket ids.
func Create ¶
Create writes a new epic with its first members. The name must be normalized already, and ids must be full ticket ids.
func FromBytes ¶
FromBytes reads an epic out of what its ref carries. The name comes from the ref, never from the content: a name inside the file would be a second truth that an edit could make disagree with the ref it lies on.
func LoadAll ¶
LoadAll reads every epic, sorted by name. An epic whose ref cannot be read is skipped rather than failing the listing.
func Set ¶ added in v0.5.5
Set rewrites an epic's title, description and text, in one write of its ref and leaving its members as they are. It returns the epic as it stands afterwards and whether anything was written.
func (*Epic) Add ¶
Add appends one member line. A ticket already in the epic is left where it is: re-adding must not reorder a list somebody arranged by hand.
func (*Epic) Body ¶ added in v0.5.5
Body is the markdown below the frontmatter, every member line taken out and the blank lines around it trimmed: the text a person wrote about the feature. The member lines are the list, and the list is edited with Add and Remove, not as text.
type Fields ¶ added in v0.5.5
type Fields struct {
Title, Description, Body string
}
Fields are what a person writes about an epic: the title, the one-line description and the text below them.
type Patch ¶ added in v0.5.5
type Patch struct {
Title, Description, Body *string
// Base is what the editor read before it changed anything; nil writes
// onto whatever is there. With it, a field the patch changes that is no
// longer what Base says refuses the whole write with ErrChanged, so an
// editor that sat open while somebody else saved cannot overwrite them.
Base *Fields
}
Patch is an edit of an epic: a nil field is left as it is.
type Progress ¶
type Progress struct {
Done int `json:"done"`
Total int `json:"total"`
Tickets []Ticket `json:"tickets"`
}
Progress is how far an epic is.
func Count ¶
Count says how far an epic is: its members, plus every ticket that follows on from one of them through follows: or parent:, however many steps down, wherever it is filed. Done is the index's Satisfied — a terminal lane or the logbook — so the epic agrees with the gate about what finished means.
Follow-ups count without a line because they appear while the work goes on: closing one ticket often opens two, and a list that had to be kept up by hand would always be behind.
type Refs ¶
type Refs interface {
// EpicNames lists every epic, unsent ones included.
EpicNames() ([]string, error)
// EpicContent reads one epic: an unsent write if there is one, else the
// local ref. A missing epic wraps ErrNotFound.
EpicContent(name string) ([]byte, error)
// RecordEpic queues an epic's whole content for its ref.
RecordEpic(name string, content []byte) error
}
Refs is where epics are kept. *refsync.Syncer implements it; a test can hand in a map.
type Ticket ¶
type Ticket struct {
ID string `json:"id"`
Title string `json:"title"`
Lane string `json:"lane"`
Done bool `json:"done"`
// Direct is true for a member line, false for a ticket that counts
// because it follows on from one (follows: or parent:).
Direct bool `json:"direct"`
}
Ticket is one ticket counted towards an epic.