Documentation
¶
Overview ¶
Package outbox holds the ticket writes that have not reached the remote yet.
It exists because of a decision about how the tool should feel: claiming a ticket works offline. Being offline is rare enough that it must not dictate the interaction — so a claim is written locally straight away and the push is caught up when there is a network again. Something has to hold the write in between, and that is this.
It also settles a problem that would otherwise sit in the write path. Every ticket write funnels through core/ticket's Mutate and Create, and those are called from the TUI and from the merge driver as well as from the CLI. A synchronous push there would hang the board on the network and would have the merge driver pushing in the middle of a git merge. With an outbox, the write path only files the write; sending happens outside it.
The outbox lives under the per-working-tree state directory, not in the repository: an unsent write is this machine's business, and committing it would publish it in the very moment it is meant to be waiting.
Index ¶
- type Box
- func (b *Box) Drop(id string) error
- func (b *Box) Flush(s Sender) ([]Result, error)
- func (b *Box) List() ([]Entry, error)
- func (b *Box) Pending(id string) (Entry, bool)
- func (b *Box) Queue(id string, op Op, content []byte, lease, by string) error
- func (b *Box) QueueMilestone(name string, op Op, content []byte, lease, by string) error
- type Entry
- type Kind
- type Op
- type Outcome
- type Result
- type Sender
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Box ¶
type Box struct{ Dir string }
Box is the queue for one working tree.
func (*Box) Flush ¶
Flush sends what it can and reports each entry.
It stops at the first unreachable remote instead of walking the rest of the queue. The network is a property of the machine, not of the ticket: once one push has proved there is no route, every following attempt would wait for the same timeout, and a command the user ran for another reason entirely would sit there for as long as the queue is.
A lost race is the opposite case and does not stop anything: it is about one ticket, and the next one may well go through.
func (*Box) List ¶
List returns every unsent write, oldest first. Ticket ids are ULIDs, so sorting by id is chronological by creation; QueuedAt orders by when the write was filed, which is what a flush should follow.
func (*Box) Pending ¶
Pending returns the unsent write for one ticket, if there is one. The board asks this to mark a card as carrying something not yet sent.
func (*Box) Queue ¶
Queue files a write, superseding any earlier unsent write for the same ticket. Superseding is correct rather than lossy: the entry carries the whole ticket, so the newer bytes already contain everything the older ones said.
func (*Box) QueueMilestone ¶ added in v0.3.0
QueueMilestone files a pending write of a milestone file, under the same rules: whole content, the lease the remote last confirmed, superseding any earlier unsent write of the same milestone.
type Entry ¶
type Entry struct {
ID string `json:"id"`
Op Op `json:"op"`
Ref string `json:"ref"`
// Kind says which namespace ID belongs to. Absent on an entry written
// before there were two, which means a ticket.
Kind Kind `json:"kind,omitempty"`
// Lease is the ref SHA the remote last confirmed to us, and it is the
// compare-and-swap value. It survives being superseded: a second local
// write while the first is still unsent replaces the content but keeps
// this, because the question the remote answers is "has anyone written
// since I last saw it", and our own unsent write is not an answer to that.
Lease string `json:"lease"`
// Content is the whole ticket file, not a patch. The file is the API, and
// a queued write that replayed a diff could apply cleanly onto a state
// nobody ever reviewed.
Content string `json:"content,omitempty"`
QueuedAt time.Time `json:"queued-at"`
UpdatedAt time.Time `json:"updated-at"`
By string `json:"by,omitempty"`
}
Entry is one ticket's pending write.
type Kind ¶ added in v0.3.0
type Kind string
Kind is which namespace a queued write belongs to: a ticket, keyed by its ulid, or a milestone, keyed by its name.
It is part of the path an entry is filed under, not only a field inside it. One flat directory keyed by the bare name would collide the moment somebody named a milestone after a ticket handle — and the collision would be silent, one unsent write overwriting the other.
const ( // KindTicket is a ticket's own ref. It is the zero value's meaning too, so // an entry queued by an older build — which had no kinds and no // subdirectory — still reads back as the ticket write it is. KindTicket Kind = "tickets" // KindMilestone is a milestone file's ref. KindMilestone Kind = "milestones" )
type Outcome ¶
type Outcome string
Outcome is what became of one queued write during a flush.
const ( // Sent means the remote accepted it and the entry is gone. Sent Outcome = "sent" // Rejected means someone else wrote the ticket first. The entry is // dropped: replaying it would only lose again, and the local file is still // there for the board to merge the ref into. Rejected Outcome = "rejected" // Unsent means the remote could not be reached. The entry stays. Unsent Outcome = "unsent" // Failed means git refused for some other reason. The entry stays, because // the cause is unknown and discarding a write on an unknown error is the // one outcome that loses work. Failed Outcome = "failed" )
type Sender ¶
type Sender interface {
Write(id string, content []byte, lease string) (string, error)
Delete(id, lease string) error
WriteMilestone(name string, content []byte, lease string) (string, error)
}
Sender is the transport a flush pushes through. It is an interface so the queue's behaviour — what it keeps, what it drops, when it stops — can be tested without a remote, and so nothing in here depends on a network being reachable.