Documentation
¶
Overview ¶
Package gitref carries a ticket on a git ref of its own, outside refs/heads.
The problem it solves: a ticket file lives in the branch of whoever wrote it. If that branch is unpushed, stale or force-pushed, the person the ticket was assigned to sees nothing — not the title, not even the id. Scanning every branch for ticket files is expensive and still wrong.
A ref under refs/jaira/tickets/<id> belongs to no branch. The default refspec does not fetch it, so a clone by someone who does not use jaira is unaffected, and the push onto it is a compare-and-swap that uses the ref lock the hosting provider already implements. No server, no daemon: the remote that is already there is the channel.
The ref's tree carries the ticket file itself rather than only its id, so a colleague reads the whole ticket without a branch and without a checkout:
git show refs/jaira/tickets/<id>:<id>.md
The ticket file also stays in the code commit, and the two answer different questions: the file in the commit says what this change was and why, the ref says where the ticket stands right now and with whom. Same bytes, one of them on a channel that is visible without the branch.
Index ¶
- Constants
- Variables
- func RefName(id string) string
- type Repo
- func (r *Repo) BlobsOf(ids []string) (map[string]string, error)
- func (r *Repo) Commit(tree, parent, message string) (string, error)
- func (r *Repo) Delete(id, lease string) error
- func (r *Repo) Fetch() error
- func (r *Repo) FetchBranch(branch string) error
- func (r *Repo) Landed(id string, branches []string) string
- func (r *Repo) List() ([]string, error)
- func (r *Repo) ListRemote() ([]string, error)
- func (r *Repo) LsTree(rev, dir string) ([]string, error)
- func (r *Repo) Nest(dir, tree string) (string, error)
- func (r *Repo) PushBranch(branch, commit, lease string) error
- func (r *Repo) Read(id string) ([]byte, string, error)
- func (r *Repo) ReadMany(ids []string) (map[string][]byte, error)
- func (r *Repo) ReadParent(id string) ([]byte, error)
- func (r *Repo) RemoteHead() string
- func (r *Repo) Rev(rev string) string
- func (r *Repo) SHA(id string) (string, error)
- func (r *Repo) SetRemoteHead() error
- func (r *Repo) Tree(entries map[string]string) (string, error)
- func (r *Repo) TreeOf(rev string) string
- func (r *Repo) Usable() error
- func (r *Repo) Write(id string, content []byte, lease string) (string, error)
Constants ¶
const DefaultRemote = "origin"
DefaultRemote is where refs go when nothing is configured.
const Prefix = "refs/jaira/tickets/"
Prefix is the ref namespace. It is outside refs/heads deliberately: the default refspec does not fetch it, so these refs are invisible to anyone who does not ask for them, and they never show up as branches or in a hosting provider's web UI.
Variables ¶
var ( // ErrRaceLost means someone else wrote the ref after we read it. The // caller has stale bytes and must re-read; it is not a transport failure // and retrying the same push would only lose again. ErrRaceLost = errors.New("gitref: the ticket moved since it was read") // ErrOffline means the remote could not be reached at all. The write is // not lost and not refused — it is unsent, which is what the outbox is // for. ErrOffline = errors.New("gitref: the remote is unreachable") // ErrNoRef means the ticket has no ref yet. ErrNoRef = errors.New("gitref: no ref for this ticket") // ErrNoRepo means this board is not in a git repository, or the remote it // was told to use does not exist. It is not a failure to report at every // write: a board without a repository is a supported way to use jaira, it // simply does not carry tickets on refs. ErrNoRepo = errors.New("gitref: no repository or no such remote") // ErrNoGit means the git binary is unavailable. ErrNoGit = errors.New("gitref: git is not available on PATH") )
Functions ¶
Types ¶
type Repo ¶
type Repo struct {
Dir string
Remote string
// Author names the identity written onto the ref commit. It is separate
// from git config on purpose: these commits are made in the background of
// somebody else's command, and a machine with no user.name configured (CI,
// a fresh container) must still be able to write a ticket rather than fail
// with git's "please tell me who you are".
AuthorName string
AuthorEmail string
}
Repo is a working tree together with the remote its ticket refs live on.
func (*Repo) BlobsOf ¶
BlobsOf resolves the blob each ticket ref carries, in one git invocation.
A snapshot reuses these blobs rather than hashing the content again: the objects are already in the repository, put there by the ref writes, so a snapshot adds a tree and a commit and nothing else.
func (*Repo) Delete ¶
Delete removes the ticket's ref, locally and on the remote. A logged or archived ticket is off the board, and leaving its ref behind would keep it on everybody else's.
func (*Repo) Fetch ¶
Fetch brings every ticket ref up to date in one roundtrip. The refspec is explicit because the default one does not carry these refs — which is the property that keeps them invisible to people who do not use jaira.
--prune is part of the contract, not a tidy-up: a ticket that was logged or archived deletes its ref, and without pruning that ticket would sit on everyone else's board forever, deleted for its owner and immortal for everyone else.
func (*Repo) FetchBranch ¶
FetchBranch brings one branch from the remote into a local ref of the same name, so a snapshot can be appended to what the remote already has.
func (*Repo) Landed ¶
Landed reports the commit in which a ticket was filed away on one of the given branches, or "" if it has not arrived there.
Only the filed-away paths count. A ticket still sitting under tickets/ on that branch has not landed in this sense: it is on the board there, which is the opposite of finished.
func (*Repo) ListRemote ¶
ListRemote asks the remote which tickets exist, without fetching them. One roundtrip is cheap enough to run before a command needs the contents.
func (*Repo) PushBranch ¶
PushBranch moves a branch on the remote to commit, refusing if the remote moved since lease was read. Used for the snapshot branch, where two people snapshotting at once must not overwrite one another.
func (*Repo) ReadMany ¶
ReadMany returns the ticket files carried by many refs, in two git invocations rather than two per ticket.
Measured on this project: reading a hundred tickets one at a time costs about a quarter of a second, almost all of it process startup, while one cat-file --batch pass is not measurable. The board reads every ticket on every refresh, so the per-ticket shape would grow into the one thing this tool must never be — slow to open.
func (*Repo) ReadParent ¶
ReadParent returns the ticket file as it was one commit earlier on the ref — the state this clone had already seen when it last wrote or fetched.
This is what makes reconciling the local file with the ref a real three-way merge rather than a guess about which side is newer: because every ref commit takes the leased SHA as its parent, the parent's blob is exactly "the state both sides started from". A ref with no parent (the ticket's first write) has no such state, and reports ErrNoRef so the caller can fall back to two-way.
func (*Repo) RemoteHead ¶
RemoteHead returns the remote's default branch as a revision (origin/master and the like), or "" if the clone does not know it.
A clone of a repository that has commits knows this without asking anybody; a clone of an empty one does not, and cannot be told until the remote has a branch at all. So this answers from what is already local, and SetRemoteHead is the separate, network-touching way to fill it in later.
func (*Repo) SHA ¶
SHA returns the commit the ticket's local ref points at, or ErrNoRef. This SHA is the lease: every write is a compare-and-swap against the value the writer last read.
func (*Repo) SetRemoteHead ¶
SetRemoteHead asks the remote which branch it points HEAD at and records it, so RemoteHead can answer locally from then on. One round trip, ever.
func (*Repo) Tree ¶
Tree writes a tree object holding the given entries, and returns its hash. The entries are name to blob, all regular files.
func (*Repo) Usable ¶
Usable reports whether this working tree can carry tickets on refs at all: git present, inside a repository, and the remote actually configured.
Asked once by the caller rather than inferred from a failed write, because the two are different answers to different questions — "this board does not use refs" is a permanent property of the checkout, while a refused push is about one ticket at one moment.
func (*Repo) Write ¶
Write puts the ticket file on the ref and pushes it, refusing if the remote moved since lease was read.
The commit takes the leased SHA as its parent when there is one, so the ref keeps the ticket's history (git log on the ref is the ticket's movement) and a stale write is a non-fast-forward that git rejects on its own. --force-with-lease is still passed: it is the check that holds when a ticket is written for the first time by two people at once, where neither commit has a parent to compare.
An empty lease means "no ref yet" and requires the remote to have none either.