Documentation
¶
Overview ¶
Package milestone holds the groups a board plans in: which tickets belong together for this round of work, kept in one file per group under .jaira/milestones/.
The grouping lives in its own file rather than as a field on each ticket because of what happens at the end of a round. Work that did not finish has to appear in the next group, and a per-ticket marker means touching every ticket one at a time — and tickets travel on their own refs, so that is one pull, one edit and one release each. Twenty of those is the reason nobody does it. A file is copied instead: the lines that are not finished move into the next milestone's file in one edit.
A milestone that has been filed into the logbook carries `status: filed` in its frontmatter, and that line is what keeps it off the board. It is written onto the ref too, on purpose: a ref taken down would free the name again and tell no other clone anything, while a ref saying "filed" is read by every clone that fetches — it stops the file being written back to disk, and it holds the name until somebody restores it. No line means the milestone is on the board, which is the state every file written before this existed is in.
The file is read and written the way a ticket file is — frontmatter for the facts about the milestone, one line per member below it — and the lines are kept verbatim across a write. A comment, a blank line, a hand-chosen order and a line jaira cannot parse all survive Add and Remove, because those edit one line and leave the rest alone. The format is the API: it must stay hand-editable and readable in a diff, and one line per member is the smallest thing git can merge.
Index ¶
- Constants
- Variables
- func AssignColour(existing []*Milestone, name string) int
- func Dir(root string) string
- func NormalizeName(raw string) (string, bool, error)
- func Path(root, name string) string
- type Index
- type Milestone
- func (m *Milestone) Add(id string) bool
- func (m *Milestone) Bytes() []byte
- func (m *Milestone) Filed() bool
- func (m *Milestone) Has(id string) bool
- func (m *Milestone) HasColour() bool
- func (m *Milestone) Members() []string
- func (m *Milestone) Remove(id string) bool
- func (m *Milestone) Save(root string) error
- func (m *Milestone) SetStatus(status string)
Constants ¶
const StatusFiled = "filed"
StatusFiled is the one status a milestone can carry. A milestone is either on the board or filed into the logbook, so the field is a flag with a name rather than a state machine — and a name, because a reader of the file has to be able to tell what it means without a table.
const Subdir = ticket.MilestonesSubdir
Subdir is where a board keeps its milestones, under .jaira. It is the name core/ticket files a milestone under inside a logbook folder too, and is taken from there so the two cannot drift apart.
Variables ¶
var Palette = tag.Palette
Palette is the set of colours a new milestone can be given. It is the tag palette: the two marks sit on opposite edges of a card and are never confused for one another by position, so spending a second set of sixteen values would only push both closer to the colours the board already spends on status.
Functions ¶
func AssignColour ¶
AssignColour picks a colour for a new milestone: a random one none of the existing milestones is using, so two groups on one board stay distinguishable. Random rather than next-in-sequence because a sequence gives every board the same first colours, and nobody should have to choose one. Once the palette is spent it repeats, derived from the name so the repeat is at least stable across machines.
func NormalizeName ¶
NormalizeName turns a written milestone name into the form it is stored and filed under: lowercase kebab, exactly the tag rules. A milestone name is a filename, so the charset has to be one that is safe in a path on every platform — and reusing the tag rules means one answer to "what may I call this" instead of two that differ in a corner.
Types ¶
type Index ¶
Index answers, for one ticket id, which milestones hold it — the direction every reader needs and the file does not give. Multiple membership is allowed: a ticket carried over from one round into the next is in both files until somebody removes the old line.
func Build ¶
Build indexes milestones by ticket id, keeping the board's file order within each ticket so the marks on a card are painted the same way everywhere.
type Milestone ¶
type Milestone struct {
Name string
Colour int
CreatedAt time.Time
Status string
// contains filtered or unexported fields
}
Milestone is one file as it was read: every line kept verbatim beside the facts parsed out of them.
func FromBytes ¶
FromBytes reads a milestone out of bytes that are not on this disk — what a ref carries. The ref is the only place a milestone filed on another machine can be read from, and the caller has to be able to ask its status without first writing it to the board, which is the very thing a filed milestone must not do.
func Load ¶
Load reads one milestone by name. A missing file reports os.ErrNotExist, so a caller can tell "no such milestone" from "unreadable".
The file name is the milestone's name, always — the frontmatter does not carry one. The file is hand-editable by design, and a name written inside it would be a second truth: edit that line and the next Save files the milestone under a different name, leaving the old file and its ref behind.
func LoadAll ¶
LoadAll reads every milestone that is ON the board, sorted by name. An absent directory is not an error: it means the board has planned no milestones yet, which is the state every board starts in.
A filed milestone is not on the board and is left out here, which is what makes `status: filed` the line that decides it rather than where the file happens to lie. Both states put a file under .jaira/milestones/: filing it moves it into the logbook, but a fetch writes the marked file back the way it writes every other one, and a filing that got as far as marking the file and no further has left it lying here too. One read of the line answers all three. Load reads a filed milestone by name, which is how a restore gets at it.
A file that does not parse is skipped rather than failing the read: the board is glanced at constantly, and one malformed file must not be able to stop it from opening.
func New ¶
New builds a milestone that has never been written, with the colour the caller picked. It is not on disk until Save.
func (*Milestone) Add ¶
Add records a ticket as a member, appending one line. A ticket already in the file is left where it is: re-adding must not reorder a list somebody arranged by hand.
func (*Milestone) Bytes ¶
Bytes renders the milestone as the file it is: every line as it was read or edited, newline-terminated. It is what Save writes, and it is what the ref carries — the caller that queues the ref already holds the milestone, so it spells the content from here rather than reading back the file Save just wrote.
func (*Milestone) Filed ¶
Filed reports whether this milestone has been put into the logbook. A filed milestone is off the board: 'jaira milestone ls' does not name it, no card carries its colour, and a fetch that finds its ref leaves the ref alone rather than writing the file back to disk.
func (*Milestone) HasColour ¶
HasColour reports whether this milestone paints a cell on a card. Zero is "no colour" rather than ANSI black: the board reserves the right-hand column whether a ticket is in a milestone or not, so something has to mean "leave the cell blank", and 0 is that value everywhere it is asked.
func (*Milestone) Members ¶
Members lists the ticket ids this milestone holds, in the order the file gives them. File order is the order the card's marks are painted in, so it is a thing a person controls by editing.
func (*Milestone) Save ¶
Save writes the milestone back through ticket.WriteAtomic, so a reader never sees a half-written file and a crash leaves the previous one intact. Atomicity is not exclusion: two writers still have to be serialised by the caller.
func (*Milestone) SetStatus ¶
SetStatus writes, replaces or removes the status line in the frontmatter and touches nothing else — the same rule Add and Remove follow. Rebuilding the frontmatter from the parsed facts would be shorter and would throw away the comments and the order somebody chose, which is exactly what the format promises to keep.
The empty status removes the line, which is how a restore puts a milestone back on the board. A file with no frontmatter at all is given one, because there is nowhere else for the status to go and a hand-written file is allowed to have started without it.