zit has been able to read an attachment for a long time. The A card
folds, zit artifact describes it, a clone carries it, and a fossil-hosted
project's screenshots and patches come across a sync intact. What zit could
not do was write one — and every other thing quarry authors goes through a
zit verb. ticket new, wiki commit, forum reply, technote add: the
artifact format is written in one place, by one writer, and read by fossil
on the other side. Attachments were the exception, and an exception in a
format writer is where the second implementation starts.
0.3.13 closes it.
The verbs
zit attach add TARGET FILE [--name NAME] [--comment TEXT]
[--mimetype TYPE] [--user NAME] [-R repo]
zit attach rm TARGET NAME [--user NAME] [-R repo]
zit attach list [TARGET] [-R repo]
add stores the file as a blob, then writes the attachment artifact —
one A NAME TARGET SOURCE card, a comment if you gave one, the writer
clock, a mimetype if you gave one, the author, the checksum — and prints
the artifact's hash and the blob's on two lines. The two writes happen in
one commit, which is the detail worth noticing: there is no moment at
which the store holds an attachment naming bytes it does not have, and no
crash that leaves one behind.
TARGET is what fossil accepts: a ticket, technote, or forum post's hash,
or a wiki page's name. It is checked for shape and nothing more, because
that is what the format says — an unresolved target is recorded as a
generic reference, and a verb that refused to attach to a ticket it could
not find would be inventing a rule fossil does not have.
rm writes an A card with no source. Nothing leaves the store; the
association ends under the format's latest-wins rule, and list — which
folds the newest card per name — stops showing it. list prints
NAME SIZE MIMETYPE BLOB DATE USER, with the target as a first column when
you ask for the whole repository, which is what an export or an audit walks.
The cap, and why it is lower than fossil's
Fossil has a setting called attachment-size-limit: a byte count checked
before the blob is stored, 0 meaning unlimited, and unlimited is its
default. zit honours the same setting the same way, with one deliberate
difference — unset, the limit is 1 MiB.
A blob in the DAG travels to every clone, for ever. Fossil's default is
tuned for a project whose attachments are screenshots and patches, uploaded
by people who know what a repository is. quarry is a forge, and a forge's
first-time user will drag a 400 MB screen recording onto a ticket. The
right default is the one that says no to that, names the size and the
limit, and tells you how to lift it: zit settings attachment-size-limit 0.
The setting lives in the repository's config frame under a new key. A zit that predates it reads the repository without complaint and simply sees the setting as unset.
What quarry attaches instead
quarry will never write a file's bytes into a hosted repository through this verb. It attaches a stub — a small text blob that says where the bytes are:
quarry-attachment 1
sha256 4f9a…e01c
size 18349022
type video/mp4
name screen-recording.mp4
No URL. The bytes are found by content hash in whatever storage the
repository's steward bound in quarry; the A card stays a valid fossil
artifact; the store stays small; nothing is a phantom on sync. zit now
recognises the shape: attach list shows such an attachment as
external:sha256:… with the size and type it points at, and zit artifact
on the blob says attachment-stub. A client that does not recognise it
sees a small text file under the attachment's name — degraded, but honest.
The size cap applies to what is actually stored, so a stub always passes.
Nothing on the write side changed; quarry writes the stub through the same
attach add, from stdin.
Proven against fossil
The claim that matters is not that zit writes an A card, but that fossil
reads it as an attachment. So the smoke suite now authors one with zit,
pushes it to a real fossil server, and asks fossil's own attachment
table: one live row for the target and name zit recorded, with zit's blob
as its source, and the timeline entry fossil renders — Add attachment
[note.txt] to wiki page [Home]. That check runs on every qualification.
The first run of it failed, which is worth saying because of how: the row was there, exactly right, and my query had forgotten to name the repository. The interop was never in doubt once the data was looked at. It was the harness that needed the fix.