Artifact 8ad7be07406f4a4bcec23763af3b765bc8accfd78284f4c24912c19ade49f800:
- File
www/reference.html
— part of check-in
[4652c5cc31]
at
2026-08-21 16:51:33
on branch trunk
— timeline: --weekday, Ben Joffe's branchless day-of-week (#111)
An mtime divided by a day is already the day number Joffe's identity consumes (benjoffe.com/fast-day-of-week), so the UTC weekday falls out with no calendar decomposition. Flag-gated; unflagged output is byte-identical, pinned by test against a Sakamoto reference over 40,000 days.
log --long's record grammar is now a stated contract: consumers parse it positionally and no display option will change it. The flag skips info, which prints no date line. (user: dmestas, size: 101374) [annotate] [blame] [check-ins using]
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>zit Command Reference</title> </head> <body> <style> :root { --bg: #f6f7f6; --panel: #ffffff; --ink: #1d2422; --muted: #5c6a66; --line: #dde3e0; --accent: #1f6f54; --accent-soft: #e4efe9; --flag: #8a4b08; --usage-bg: #eef1ef; --danger: #9a3b2e; --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg: #14181a; --panel: #1b2124; --ink: #e3e8e6; --muted: #93a09b; --line: #2b3336; --accent: #57b58f; --accent-soft: #1f2e28; --flag: #d99a55; --usage-bg: #101416; --danger: #d47a6a; } } :root[data-theme="dark"] { --bg: #14181a; --panel: #1b2124; --ink: #e3e8e6; --muted: #93a09b; --line: #2b3336; --accent: #57b58f; --accent-soft: #1f2e28; --flag: #d99a55; --usage-bg: #101416; --danger: #d47a6a; } * { box-sizing: border-box; } body { background: var(--bg); color: var(--ink); font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; margin: 0; padding: 0; } a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; } a:focus-visible, input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .wrap { display: flex; max-width: 1200px; margin: 0 auto; gap: 2rem; padding: 0 1.25rem; } nav { width: 215px; flex: none; position: sticky; top: 0; align-self: flex-start; max-height: 100vh; overflow-y: auto; padding: 1.5rem 0 2rem; font-size: 0.85rem; } nav .grp { margin-top: 1.1rem; } nav .grp:first-child { margin-top: 0; } nav .grp-title { text-transform: uppercase; letter-spacing: 0.09em; font-size: 0.68rem; color: var(--muted); font-weight: 600; margin-bottom: 0.25rem; } nav a { display: block; padding: 0.1rem 0.4rem; border-radius: 4px; color: var(--ink); font-family: var(--mono); font-size: 0.8rem; } nav a:hover { background: var(--accent-soft); text-decoration: none; } main { flex: 1; min-width: 0; padding: 1.5rem 0 4rem; } header.page { margin-bottom: 1.5rem; } h1 { font-size: 1.7rem; margin: 0 0 0.3rem; letter-spacing: -0.01em; } h1 .z { color: var(--accent); font-family: var(--mono); } .sub { color: var(--muted); max-width: 60ch; margin: 0; } #filter { width: 100%; max-width: 32rem; margin: 1.1rem 0 0; padding: 0.55rem 0.8rem; font: 0.9rem var(--mono); color: var(--ink); background: var(--panel); border: 1px solid var(--line); border-radius: 8px; } #filter::placeholder { color: var(--muted); } .conv { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 1rem 1.25rem; margin: 1.5rem 0; font-size: 0.9rem; } .conv h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--muted); margin: 0 0 0.6rem; } .conv ul { margin: 0; padding-left: 1.1rem; } .conv li { margin: 0.3rem 0; } h2.grp-head { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.11em; color: var(--accent); border-bottom: 1px solid var(--line); padding-bottom: 0.35rem; margin: 2.6rem 0 1rem; } .cmd { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 1.1rem 1.3rem 1.2rem; margin-bottom: 1rem; } .cmd h3 { margin: 0 0 0.15rem; font-size: 1.05rem; font-family: var(--mono); } .cmd h3 .v { color: var(--accent); } .cmd h3 .alias { color: var(--muted); font-size: 0.75rem; font-weight: 400; margin-left: 0.5rem; } .one { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.7rem; } .usage { background: var(--usage-bg); border-radius: 6px; padding: 0.55rem 0.8rem; font: 0.8rem/1.5 var(--mono); overflow-x: auto; white-space: pre; margin: 0 0 0.75rem; } .desc { font-size: 0.92rem; margin: 0 0 0.75rem; max-width: 72ch; } .desc p { margin: 0 0 0.5rem; } .desc p:last-child { margin-bottom: 0; } .warn { color: var(--danger); font-weight: 600; } table.flags { border-collapse: collapse; width: 100%; font-size: 0.85rem; } table.flags td { padding: 0.28rem 0.9rem 0.28rem 0; vertical-align: top; border-top: 1px solid var(--line); } table.flags tr:first-child td { border-top: none; } table.flags td.f { font-family: var(--mono); font-size: 0.8rem; color: var(--flag); white-space: nowrap; } code { font: 0.85em var(--mono); background: var(--usage-bg); padding: 0.08em 0.35em; border-radius: 4px; } .hidden { display: none; } #nores { color: var(--muted); font-style: italic; display: none; margin-top: 2rem; } nav summary { font-family: var(--mono); font-size: 0.8rem; cursor: pointer; color: var(--accent); padding: 0.15rem 0.4rem; border-radius: 4px; list-style: none; } nav summary::-webkit-details-marker { display: none; } nav summary::before { content: "\25B8 "; display: inline-block; transition: transform 0.12s; } nav details[open] > summary::before { transform: rotate(90deg); } nav summary:hover { background: var(--accent-soft); } /* On the wide layout the rail is always open, so the control is noise. */ @media (min-width: 801px) { nav summary { display: none; } } @media (prefers-reduced-motion: reduce) { nav summary::before { transition: none; } } /* Narrow viewports (an embedded doc panel is routinely under 800px): the sidebar stops being a sticky rail and becomes a compact, scrollable index above the content. It used to be `display: none`, which left a 60-section reference with no navigation at all in exactly the context where scrolling hurts most. */ @media (max-width: 800px) { .wrap { display: block; } nav { position: static; width: auto; max-height: 40vh; overflow-y: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 0.8rem 1rem; margin-bottom: 1.6rem; } /* Links flow as wrapped chips rather than one per line, so the whole verb set stays glanceable instead of becoming a long column. */ nav .grp { display: flex; flex-wrap: wrap; gap: 0.25rem 0.5rem; align-items: baseline; margin-top: 0.7rem; } nav .grp-title { width: 100%; margin-bottom: 0.1rem; } nav a { padding: 0.08rem 0.35rem; } } @media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } </style> <div class="wrap"> <nav id="toc"></nav> <main> <header class="page"> <h1><span class="z">zit</span> Command Reference</h1> <p class="sub">Every verb and flag of zit — a Fossil version-control system in Zig. One self-contained repository file (<code>.zit</code>), no staging area, immutable history.</p> <input id="filter" type="search" placeholder="filter commands, flags, or text… (e.g. --force, branch, shun)" aria-label="Filter commands"> </header> <div class="conv"> <h2>Conventions shared by every command</h2> <ul> <li><code>-h, --help</code> — every verb prints its own help and exits.</li> <li><b>Help topics</b> — <code>zit help TOPIC</code> covers cross-cutting subjects: <code>names</code> (every accepted check-in-name form, timestamps included), <code>sparse</code> (the cone model), <code>repo</code> (<code>-R</code>/<code>--no-cache</code>), <code>exit-codes</code> (the contract and its departures). A typo'd topic refuses with exit 2 instead of falling back to the command list.</li> <li><b>Exit status</b> — <code>0</code> success, <code>1</code> operational failure (not a repo, missing file, refused action), <code>2</code> usage error (unknown option, wrong operands). Every verb's help now states its contract; <code>frames</code> departs (3 = nothing new, 4 = cursor invalid) and <code>zit help exit-codes</code> lists the departures.</li> <li><code>-R, --repo <path></code> — read-only inspectors (<code>log</code>, <code>timeline</code>, <code>leaves</code>, <code>branch</code>, <code>tag</code>, <code>info</code>, <code>ls</code>, <code>cat</code>, <code>artifact</code>, <code>annotate</code>, <code>frames</code>, <code>verify</code>, <code>tarball</code>, <code>zip</code>) accept a repository path directly; no working tree required. <code><path></code> is the directory holding <code>.zit</code> or the <code>.zit</code> file itself.</li> <li><code>--no-cache</code> — derived-state readers (<code>leaves</code>, <code>branch</code>, <code>tag</code>, <code>info</code>, <code>ls</code>, <code>cat</code>) can bypass every sidecar (cache and rebuild checkpoint) and re-derive from the full log, re-verifying whole-log integrity.</li> <li><b>Name resolution</b> — wherever a check-in name is accepted: a full 40/64-char artifact ID, a unique prefix, a branch or tag name, reserved forms <code>tip</code>, <code>rid:</code>, <code>tag:</code>, <code>root:</code>, <code>start:</code>, <code>merge-in:</code>, or a <b>timestamp</b> — <code>2026-08-01</code>, <code>2026-08-01T14:30:00Z</code>, <code>date:…</code> — resolving to the youngest check-in at or before that moment, and the compound <code>BRANCH:TIMESTAMP</code> (<code>trunk:2026-08-01</code>) scoping it to one branch. <code>zit help names</code> is the authoritative statement.</li> <li><b>Autosync</b> — with the default <code>autosync on</code> and a saved remote, <code>commit</code> pulls before and pushes after, and <code>update</code>/<code>merge</code> pull first. Failures warn and the local operation proceeds; <code>--no-sync</code> skips it once; no saved remote means silence, not a nag.</li> <li><b>Sparse cones</b> — <code>clone</code>/<code>open</code>/<code>update</code> accept <code>--cone=PATH</code> (repeatable; files or directories), <code>--cone-not=PATH</code> exclusions, and <code>--cone-file=FILE</code>. The most specific member decides coverage — <code>zit cone test PATH</code> reports the verdict and names the member that produced it. A sparse <i>checkout</i> materializes and tracks only the cone; a sparse <i>store</i> holds only what the cone needs, the rest deliberately elided and refetchable by widening. Losing sparse state heals toward dense — bandwidth, never data.</li> <li><b>Timestamps</b> — every artifact-minting verb writes its D card with millisecond precision (<code>YYYY-MM-DDTHH:MM:SS.SSS</code>, fossil's canonical form), so same-second writes keep real-time order for latest-wins readers; <code>SOURCE_DATE_EPOCH</code> pins <code>.000</code> for reproducible builds.</li> <li><b>Authenticated sync</b> — an authenticated exchange carries a fresh credential on every round: a body login until the peer's version gate is known, then a per-round <code>x-f-l-c</code> cookie whose nonce covers that round's own body; <code>zit serve</code> verifies both channels and refuses tampered, replayed, or duplicate logins.</li> <li><b>Signatures</b> — <code>zit commit --sign</code> clearsigns the manifest and stores the <i>wrapped</i> text as the artifact, so its name covers the signature and every reader strips the wrapper to reach the cards; a signed check-in therefore survives pack, verify, and sync byte-exact. The signer follows fossil's contract — invoked as <code>COMMAND OUTFILE INFILE</code>, an <code>ssh</code>-family basename taking the detached path with zit assembling the armor, <code>off</code> disabling — and is chosen widest-scope-last: <code>$ZIT_PGP_COMMAND</code>, else the repository's <code>pgp-command</code> setting, else <code>gpg --clearsign -o </code>. Setting <code>clearsign on</code> signs every commit without <code>--sign</code>; both settings are deliberately local and never propagate (see <a href="#settings"><code>settings</code></a>). A signer that fails or alters the manifest refuses the commit rather than storing it unsigned. <code>zit artifact</code> reports signature <i>presence</i>; validity stays your own <code>gpg --verify</code> over <code>artifact --raw</code>.</li> <li><b>Worktrees</b> — <code>zit open --store=PATH</code> opens additional working trees of one repository. All trees share the store and its cache; per-tree state (branch, cone, undo, bisect) stays independent, and concurrent commits from different trees serialize on the store's lock.</li> <li><b>No staging area</b> — <code>add</code>/<code>rm</code> record <i>which</i> files are tracked; contents are read from the working tree at <code>commit</code> time.</li> </ul> </div> <!-- ===================== GETTING STARTED ===================== --> <h2 class="grp-head" data-grp="Getting started">Getting started</h2> <section class="cmd" id="init"> <h3><span class="v">zit init</span></h3> <p class="one">Create an empty zit repository.</p> <pre class="usage">zit init [-q|--quiet] [--] [directory]</pre> <div class="desc"><p>Writes <code>.zit</code> (the repository — a single, self-contained content log) and <code>.zit-checkout</code> (the working-state record) into <i>directory</i> (default: current dir; created if missing). The default branch <code>trunk</code> is unborn until the first commit. Artifacts are named with SHA3-256.</p></div> <table class="flags"> <tr><td class="f">-q, --quiet</td><td>do not print the confirmation line</td></tr> </table> </section> <section class="cmd" id="clone"> <h3><span class="v">zit clone</span></h3> <p class="one">Copy a repository from a URL into a new directory.</p> <pre class="usage">zit clone URL DIR [--once] [--cone=PATH]... [--cone-not=PATH]... [--cone-file=FILE]</pre> <div class="desc"><p>Creates DIR and its <code>.zit</code>, syncs the peer's artifacts down to convergence over HTTP. The origin's <b>shun table</b> is adopted with it (see <a href="#shun"><code>shun</code></a>), so a clone can never come up serving content its origin was ordered to remove; the adopted count is reported. URL is the peer's sync endpoint, used verbatim. Anonymous — no login. The URL is saved as DIR's default remote (canonical form — a <code>user:password@host</code> URL is stored as <code>user@host</code>, password structurally excluded), so <code>pull</code>/<code>push</code>/<code>sync</code> work with no URL. On a terminal, per-round progress shows on stderr with a closing rounds/bytes accounting line. Follow with <code>zit open</code> inside DIR to fill the working tree.</p></div> <table class="flags"> <tr><td class="f">--once</td><td>do not save URL as the default remote</td></tr> <tr><td class="f">--cone=PATH</td><td>clone <b>sparse</b>: fetch only the artifacts PATH's subtree needs; everything else stays a deliberately-elided phantom (repeatable — files and directories both work). A cone-aware server filters what it sends; against any other server the clone filters locally, so it degrades to bandwidth, never to error.</td></tr> <tr><td class="f">--cone-not=PATH</td><td>exclude PATH's subtree from an enclosing <code>--cone</code> member ("all of src/ except src/vendor/"); the most specific member decides coverage, and a deeper re-inclusion under an exclusion works</td></tr> <tr><td class="f">--cone-file=FILE</td><td>read cone members from FILE, one per line — <code>#</code> comments and blank lines skipped, a <code>!</code> prefix marks an exclusion; combines with the flags</td></tr> </table> </section> <section class="cmd" id="open"> <h3><span class="v">zit open</span></h3> <p class="one">Fill a freshly cloned directory with its files.</p> <pre class="usage">zit open [CHECKIN] [--store=PATH] [--manifest[=r,u,t]] [--cone=PATH]... [--cone-not=PATH]... [--cone-file=FILE]</pre> <div class="desc"><p>Materializes a check-in in the working tree — the second half of <code>clone</code>. Without CHECKIN, the tip of the main branch; with one (resolved like <code>info</code> — ID, prefix, branch, tag), that check-in directly, so a fresh clone opens straight at a release tag without materializing the tip first. An already-open tree is left untouched (use <code>update</code> to move it); a repo with no check-in is already correct and reported as such — though an explicit CHECKIN must resolve, so there it fails as not found.</p> <p><b>Worktrees:</b> <code>--store=PATH</code> in an empty directory opens a <b>detached working tree</b> of the repository at PATH (the checkout holding <code>.zit</code>, or the <code>.zit</code> file itself). The new tree keeps its own checkout state — branch, cone, undo, bisect — but reads and writes the one shared store, so a commit in either tree is immediately visible to the other; concurrent commits from different trees serialize on the store's own lock (writers queue, readers never block).</p> <p><b>Re-open:</b> on a tree that is <i>already</i> open, <code>--store=NEWPATH</code> re-points it instead — the rescue for a store that moved out from under its worktrees, uncommitted work intact. The new path must hold the <i>same</i> repository (one containing this tree's current check-in); re-pointing at an unrelated store is refused.</p></div> <table class="flags"> <tr><td class="f">--store=PATH</td><td>create a detached working tree of the repository at PATH; on an already-open tree, re-point it at the (same) repository's new location</td></tr> <tr><td class="f">--manifest[=r,u,t]</td><td>write build-stamp files: <code>manifest</code> (r), <code>manifest.uuid</code> (u), <code>manifest.tags</code> (t); bare flag = <code>ru</code>. Remembered — every later <code>update</code> rewrites them. Never replaces a tracked file of the same name.</td></tr> <tr><td class="f">--cone=PATH</td><td>open <b>sparse</b>: materialize and track only PATH's subtree (repeatable). Content the store lacks is demand-fetched through the saved remote, one bounded retry.</td></tr> <tr><td class="f">--cone-not=PATH</td><td>exclude PATH's subtree from an enclosing <code>--cone</code> member; the most specific member decides</td></tr> <tr><td class="f">--cone-file=FILE</td><td>read cone members from FILE (<code>#</code> comments, <code>!</code> prefix = exclusion)</td></tr> </table> </section> <!-- ===================== WORKING TREE ===================== --> <h2 class="grp-head" data-grp="Working tree">Working tree</h2> <section class="cmd" id="add"> <h3><span class="v">zit add</span></h3> <p class="one">Mark files to include in your next check-in.</p> <pre class="usage">zit add [-n|--dry-run] [-q|--quiet] [--] <path>...</pre> <div class="desc"><p>Records only <i>which</i> files to track — contents are read at commit time. Naming a directory adds every untracked file beneath it recursively; <code>zit add .</code> adds the whole tree. Control files are never added; re-adding a tracked file is a no-op.</p></div> <table class="flags"> <tr><td class="f">-n, --dry-run</td><td>show what would be added; change nothing</td></tr> <tr><td class="f">-q, --quiet</td><td>do not print the added files</td></tr> </table> </section> <section class="cmd" id="rm"> <h3><span class="v">zit rm</span></h3> <p class="one">Stop tracking files (undo add).</p> <pre class="usage">zit rm [-n|--dry-run] [--hard] [-q|--quiet] [--] <path>...</pre> <div class="desc"><p>Removes paths from the managed set so the next check-in records their deletion. By default the working copy is left on disk (unlike git). Removing an untracked path is an error; naming a directory forgets every tracked file beneath it.</p></div> <table class="flags"> <tr><td class="f">-n, --dry-run</td><td>show what would be removed; change nothing</td></tr> <tr><td class="f">--hard</td><td>also delete the working-tree file from disk</td></tr> <tr><td class="f">-q, --quiet</td><td>do not print the removed files</td></tr> </table> </section> <section class="cmd" id="mv"> <h3><span class="v">zit mv</span></h3> <p class="one">Rename a tracked file.</p> <pre class="usage">zit mv OLD NEW</pre> <div class="desc"><p>Moves the working file, swaps the tracked-name entry, and records the rename for the next commit. OLD must be tracked and present; NEW must not be tracked or exist on disk — collisions are refused, no <code>--force</code>. Exactly two operands; never a glob, never a directory rename. Not undone by <code>zit undo</code>; reverse with <code>zit mv NEW OLD</code>.</p></div> </section> <section class="cmd" id="status"> <h3><span class="v">zit status</span></h3> <p class="one">Show the working-checkout state.</p> <pre class="usage">zit status</pre> <div class="desc"><p>Reports the branch, the current check-in, and each tracked file's standing: <code>added</code> (new, will enter the next check-in), <code>edited</code> (bytes or mode differ), <code>missing</code> (not in the working tree). Unchanged files are not listed. Doesn't report untracked files (<code>extras</code>) or deleted-and-forgotten files (<code>diff</code>).</p></div> </section> <section class="cmd" id="diff"> <h3><span class="v">zit diff</span></h3> <p class="one">Compare the working tree with the current check-in, or two check-ins.</p> <pre class="usage">zit diff [--brief] [-c N] [--from CHECKIN --to CHECKIN]</pre> <div class="desc"><p>Default output is unified hunks (git-compatible headers, three context lines, binary files noted). With <code>--from</code>/<code>--to</code>, compares two check-ins directly.</p></div> <table class="flags"> <tr><td class="f">--brief</td><td>name-status only: <code>A</code> added, <code>M</code> content/mode change, <code>D</code> removed; sorted by pathname</td></tr> <tr><td class="f">-c, --context N</td><td>hunk context lines (default 3)</td></tr> <tr><td class="f">--from CHECKIN</td><td>older operand of a check-in comparison</td></tr> <tr><td class="f">--to CHECKIN</td><td>newer operand; required with <code>--from</code></td></tr> </table> </section> <section class="cmd" id="extras"> <h3><span class="v">zit extras</span></h3> <p class="one">List files zit isn't tracking.</p> <pre class="usage">zit extras</pre> <div class="desc"><p>Every on-disk file under the checkout root that is neither tracked nor a control file — relative paths, one per line, sorted. Symlinks listed but never followed; directories themselves are not listed.</p></div> </section> <section class="cmd" id="clean"> <h3><span class="v">zit clean</span></h3> <p class="one">Delete untracked files.</p> <pre class="usage">zit clean [--dry-run] [--force]</pre> <div class="desc"><p>Deletes the set <code>extras</code> lists. <span class="warn">Destructive and not undoable.</span> By default it refuses and only reports the count. Never prompts; tracked and control files are never touched; empty directories are not pruned. If both flags are given, <code>--dry-run</code> wins.</p></div> <table class="flags"> <tr><td class="f">--dry-run</td><td>list what would be deleted; delete nothing</td></tr> <tr><td class="f">--force</td><td>delete the untracked files</td></tr> </table> </section> <section class="cmd" id="addremove"> <h3><span class="v">zit addremove</span></h3> <p class="one">Track new files and forget deleted ones in one pass.</p> <pre class="usage">zit addremove [--dry-run]</pre> <div class="desc"><p>Adds everything <code>extras</code> lists and removes tracked files missing from disk (git spells it <code>add -A</code>). Never edits a working file; reports <code>ADDED</code>/<code>REMOVED</code> per action. Modified files are membership-unchanged and left alone.</p></div> <table class="flags"> <tr><td class="f">--dry-run</td><td>report what would change; change nothing</td></tr> </table> </section> <section class="cmd" id="revert"> <h3><span class="v">zit revert</span></h3> <p class="one">Restore managed files to their checked-in contents.</p> <pre class="usage">zit revert [PATH...]</pre> <div class="desc"><p>Discards working-tree edits — all managed files, or just the named ones. Changes contents only, never tracking membership; a managed file the check-in doesn't contain is reported and left alone. Recoverable with <code>zit undo</code>.</p></div> </section> <section class="cmd" id="stash"> <h3><span class="v">zit stash</span></h3> <p class="one">Set working-tree changes aside and restore them later.</p> <pre class="usage">zit stash [save [-m MSG] | list | show [ID] | apply [ID] | pop [ID] | drop [ID]]</pre> <div class="desc"><p>Entries live in the local <code>.zit-stash</code> sidecar: durable user data, never synced, never an artifact — a reserved name, so it cannot be tracked. With no action, <code>save</code> runs. <code>apply</code> is fail-closed: the checkout must sit at the entry's own check-in and every target path must be clean — nothing is merged or overwritten.</p></div> <table class="flags"> <tr><td class="f">save</td><td>capture every added/modified/deleted file vs. the current check-in, then restore the check-in's state</td></tr> <tr><td class="f">list</td><td>one line per entry: id, check-in, file count, message</td></tr> <tr><td class="f">show [ID]</td><td>an entry's files as A/M/D lines (newest without ID)</td></tr> <tr><td class="f">apply [ID]</td><td>restore an entry's changes into the working tree (fail-closed)</td></tr> <tr><td class="f">pop [ID]</td><td>apply, then drop</td></tr> <tr><td class="f">drop [ID]</td><td>delete an entry (ids never reused)</td></tr> <tr><td class="f">-m, --message MSG</td><td>label the saved entry</td></tr> </table> </section> <section class="cmd" id="precedent"> <h3><span class="v">zit precedent</span> <span class="alias">alias: zit rerere</span></h3> <p class="one">Reuse a conflict resolution you already authored.</p> <pre class="usage">zit precedent [list | show ID | forget ID | forget --all]</pre> <div class="desc"><p>When a merge produces a conflict whose bytes match one remembered here, the remembered resolution is used instead of writing markers into the working tree. Precedents live in the local <code>.zit-precedent</code> sidecar: never synced, never an artifact, invisible to fossil. Losing the file costs one hand resolution and nothing else, so damage degrades to “no precedents” rather than failing the merge — reported, never silent.</p> <p>A precedent is keyed by the file path and the conflicted bytes together, which is exact: the conflict envelope carries all three sides. It is also whole-file, so a file whose <em>other</em> conflict changed will not replay. That under-fires on purpose — a resolution spliced into a neighbourhood that moved would be a plausible wrong answer written silently.</p> <p>To decline replay for a single merge without forgetting anything, use <code>zit merge --no-precedent</code>.</p></div> <table class="flags"> <tr><td class="f">list</td><td>one line per precedent: id, path, sizes (the default)</td></tr> <tr><td class="f">show ID</td><td>the conflict a precedent fires on, and its resolution</td></tr> <tr><td class="f">forget ID</td><td>drop one; the next such merge conflicts normally again</td></tr> <tr><td class="f">forget --all</td><td>drop every precedent</td></tr> </table> </section> <section class="cmd" id="undo"> <h3><span class="v">zit undo</span></h3> <p class="one">Undo the last update or revert.</p> <pre class="usage">zit undo</pre> <div class="desc"><p>Restores every affected file and the checkout record to their state before the last <code>update</code> or <code>revert</code>. Single level — a second <code>undo</code> in a row has nothing to undo. Does not recover <code>clean</code>, <code>mv</code>, or <code>shun</code>.</p></div> </section> <section class="cmd" id="redo"> <h3><span class="v">zit redo</span></h3> <p class="one">Reapply an undone change.</p> <pre class="usage">zit redo</pre> <div class="desc"><p>Available only immediately after an <code>undo</code>; a new update or revert clears the redo slot.</p></div> </section> <!-- ===================== CHECK-INS ===================== --> <h2 class="grp-head" data-grp="Check-ins & branching">Check-ins & branching</h2> <section class="cmd" id="commit"> <h3><span class="v">zit commit</span></h3> <p class="one">Record a check-in from your tracked files.</p> <pre class="usage">zit commit -m <message> [--user <login>] [--branch <name>] [--close] [--allow-fork] [--no-sync] [--sign] [-q|--quiet] [--no-submanifest-advice] [--] [<path>...]</pre> <div class="desc"><p>Reads each tracked file straight from the working tree — nothing to stage. With no paths, everything tracked is captured; named paths commit just those (the rest carries forward). The first commit creates the root check-in and starts <code>trunk</code>. History is immutable: no amend of content, no <code>--force</code>.</p> <p><b>Fork guard:</b> a commit whose parent is no longer its branch's leaf is refused, naming the newer tip — <code>zit update</code> to join it, or <code>--allow-fork</code> to fork deliberately. With autosync on, commit pulls first (so the guard sees the freshest DAG) and pushes the new check-in after.</p> <p>With sub-manifests enabled and pinned, a commit that drifts >15% from the analyzed optimum prints one advisory line (and interactively offers to retune) — never affects whether the commit succeeds.</p></div> <table class="flags"> <tr><td class="f">-m, --message <msg></td><td>the check-in comment (required)</td></tr> <tr><td class="f">--user <login></td><td>author to record (default: <code>$USER</code>)</td></tr> <tr><td class="f">--branch <name></td><td>start a new branch at this check-in; must not name an open branch, needs a parent to fork from</td></tr> <tr><td class="f">--close</td><td>mark this check-in a closed leaf</td></tr> <tr><td class="f">--allow-fork</td><td>commit even though the parent is no longer the branch tip — a deliberate fork</td></tr> <tr><td class="f">--no-sync</td><td>skip autosync (the pull before and push after) for this one commit</td></tr> <tr><td class="f">--sign</td><td>clearsign the manifest before storing it — see <b>Signatures</b> in the conventions above</td></tr> <tr><td class="f">-q, --quiet</td><td>do not print the check-in line</td></tr> <tr><td class="f">--no-submanifest-advice</td><td>suppress the sub-manifest drift advisory</td></tr> </table> </section> <section class="cmd" id="update"> <h3><span class="v">zit update</span></h3> <p class="one">Materialize a check-in in the working tree.</p> <pre class="usage">zit update [--no-sync] [--cone=PATH]... [--cone-not=PATH]... [--cone-file=FILE] [--dense] CHECKIN</pre> <div class="desc"><p>CHECKIN resolves like <code>info</code> — full or abbreviated ID, branch, tag, or timestamp. With autosync on and a saved remote, pulls first so CHECKIN resolves against the freshest history. Recoverable with <code>zit undo</code>.</p> <p><b>Sparse cones:</b> <code>--cone</code> replaces the checkout's stored cone (widening fetches newly covered content, demand-fetching through the saved remote when the store lacks it; narrowing dematerializes files without deleting stored content), <code>--dense</code> drops it and rematerializes everything. Absent flags inherit the stored cone.</p></div> <table class="flags"> <tr><td class="f">--cone=PATH</td><td>materialize and track only PATH's subtree (repeatable); replaces the stored cone</td></tr> <tr><td class="f">--cone-not=PATH</td><td>exclude PATH's subtree from an enclosing <code>--cone</code> member; most specific member decides</td></tr> <tr><td class="f">--cone-file=FILE</td><td>read cone members from FILE (<code>#</code> comments, <code>!</code> prefix = exclusion)</td></tr> <tr><td class="f">--dense</td><td>drop the stored cone and rematerialize the full tree</td></tr> <tr><td class="f">--no-sync</td><td>skip the autosync pull for this invocation</td></tr> </table> </section> <section class="cmd" id="cone"> <h3><span class="v">zit cone</span></h3> <p class="one">Show, amend, or query the sparse cone.</p> <pre class="usage">zit cone zit cone add PATH... zit cone remove PATH... zit cone test [--store] [--quiet] PATH...</pre> <div class="desc"><p>With no arguments, prints the worktree cone's members one per line — exclusions in the cone-file grammar (<code>!member</code>) — or <code>dense</code>. <code>add</code>/<code>remove</code> compose the stored member set with the change and drive the same materialization path as <code>update --cone</code> at the current check-in, so an add demand-fetches missing content and a remove dematerializes. <code>remove</code> of an exclusion member re-widens. Removing every member is refused — return to dense with <code>zit update --dense</code>.</p> <p><b><code>test</code></b> answers "would this cone keep this path, and why" without materializing anything. Coverage is decided by the <i>longest</i> member covering the path, across both polarities — so the deciding member is often neither the first listed nor the one a gitignore last-match-wins habit would predict, and naming it is what makes the rule legible:</p> <pre class="usage">$ zit cone test src/a.zig src/vendor/big.bin src/vendor/keep/gem.txt README covered src/a.zig src elided src/vendor/big.bin src/vendor covered src/vendor/keep/gem.txt src/vendor/keep elided README (no member covers)</pre> <p>The member prints bare, so it can be handed straight to <code>zit cone remove</code>; a named member under <code>elided</code> is necessarily an exclusion. A dense checkout reports <code>covered … (dense)</code>, which keeps the exit status honest — the file really is there. Paths are validated as worktree paths first, so one that could never be tracked is a usage error rather than a silent <code>elided</code>. Exits <code>0</code> when every path is covered and <code>1</code> when any is elided, making it usable as a predicate: <code>zit cone test build.zig && make</code>.</p></div> <table class="flags"> <tr><td class="f">add PATH...</td><td>union PATHs into the cone and rematerialize at the current check-in</td></tr> <tr><td class="f">remove PATH...</td><td>narrow the cone; files leave the tree, the store keeps their bytes</td></tr> <tr><td class="f">test PATH...</td><td>classify PATHs and name the deciding member; <code>-</code> reads paths from stdin, one per line</td></tr> <tr><td class="f">--store</td><td><code>test</code> only: query the <b>store</b> cone instead of the worktree cone. The two need not agree, and a path present in the checkout but absent from the store is a distinct condition.</td></tr> <tr><td class="f">--quiet</td><td><code>test</code> only: suppress the per-path lines, leaving only the exit status</td></tr> </table> </section> <section class="cmd" id="worktrees"> <h3><span class="v">zit worktrees</span></h3> <p class="one">List the working trees this repository serves.</p> <pre class="usage">zit worktrees</pre> <div class="desc"><p>Every tree registered against this store: the invoking tree (starred), every detached tree (<code>open --store</code>), and every co-located checkout that has opened or updated against it — each with its branch and check-in prefix. The registry is advisory and self-healing: trees stamp themselves at <code>open</code> and <code>update</code>, and the listing lazily prunes entries whose path is gone or whose checkout re-pointed at another store (membership is verified by the store file's inode, so a same-named impostor never counts). With <code>-R</code> it answers from anywhere with no working tree — "which trees does this store serve?". Move-safety warnings and cross-repo sweeps build on this data.</p></div> </section> <section class="cmd" id="merge"> <h3><span class="v">zit merge</span></h3> <p class="one">Merge another check-in into the working tree.</p> <pre class="usage">zit merge [-X ours|theirs] [--no-sync] [--no-precedent] NAME zit merge --cherrypick NAME zit merge --backout NAME</pre> <div class="desc"><p>Three-way merge, recorded as a pending merge parent — run <code>commit</code> to record it. Conflicts get markers in the files and a list on stderr; resolve and commit, or <code>undo</code> to abandon. With autosync on, pulls first.</p> <p><b><code>-X ours</code> / <code>-X theirs</code></b> resolve every <i>content</i> conflict in favour of one side instead of writing markers — the mechanical cases, like a regenerated lockfile or a vendored tree taken whole from upstream, where resolving by hand is where mistakes enter. A region only one side changed is still that side's either way: <code>-X</code> answers only what the merge could not, so it is not “discard their work”, it is “stop asking me”.</p> <p><span class="warn">-X ours is not --backout.</span> <code>--backout</code> reverses NAME's change; <code>-X ours</code> keeps your side where the two collide and reverses nothing. They read alike and do different things.</p> <p>One case <code>-X</code> deliberately declines: a file one side modified and the other deleted. That is a disagreement about whether the file should <i>exist</i>, not about its contents, and either reading silently destroys work — so it stays a conflict with or without <code>-X</code>. A strategy also leaves nothing for <a href="#precedent">precedent</a> to replay or record, since no conflict survives it.</p> <p><b>Across a sparse cone:</b> out-of-cone paths resolve at hash level with no blob reads — theirs-only changes are applied to the record (reported as "applied to the record"), ours-only carry forward, and only a path changed on both sides refuses, naming the path and the remedy (<code>zit cone add …</code> to widen).</p></div> <table class="flags"> <tr><td class="f">-X, --strategy ours|theirs</td><td>resolve content conflicts in favour of one side rather than writing markers</td></tr> <tr><td class="f">--cherrypick NAME</td><td>apply only NAME's own change onto the working tree</td></tr> <tr><td class="f">--backout NAME</td><td>reverse NAME's change on the working tree</td></tr> <tr><td class="f">--no-sync</td><td>skip the autosync pull for this invocation</td></tr> <tr><td class="f">--no-precedent</td><td>do not replay remembered conflict resolutions for this merge (see <a href="#precedent">precedent</a>); nothing is forgotten, only declined</td></tr> </table> </section> <section class="cmd" id="tag"> <h3><span class="v">zit tag</span></h3> <p class="one">List tags, name a check-in, or attach a property to one.</p> <pre class="usage">zit tag list [--raw] [CHECKIN] [--no-cache] zit tag add [--raw] [--propagate] NAME CHECKIN [VALUE] zit tag cancel [--raw] NAME CHECKIN</pre> <div class="desc"><p>A tag is one of two things, and <code>--raw</code> picks between them.</p> <p><b>Without <code>--raw</code></b>, NAME is a symbolic <i>name</i> for one check-in (stored <code>sym-</code>-prefixed): it resolves to that check-in everywhere a check-in name is accepted, and unlike a branch name later check-ins do not inherit it. Right for release names. Two check-ins carrying one name make that name ambiguous.</p> <p><b>With <code>--raw</code></b>, NAME is written verbatim as a <i>property</i>, outside name resolution entirely — right for a fact many check-ins may carry, such as a CI verdict, optionally with a VALUE. Because raw tags are not names, plain <code>list</code> omits them; <code>list --raw</code> shows every tag with its prefix intact.</p> <p><code>cancel</code> retracts a tag, taking the same <code>--raw</code> choice as the <code>add</code> that created it. Names beginning <code>wiki-</code>, <code>tkt-</code>, or <code>event-</code> are refused — fossil crosslinks wiki pages, tickets, and technotes through them. <code>ls</code> aliases <code>list</code>; <code>--no-cache</code> and <code>-R</code> are accepted, and the write paths work against a bare store.</p></div> <table class="flags"> <tr><td class="f">--raw</td><td>NAME is a raw tag name, not a symbolic check-in name; on <code>list</code>, show every tag with its prefix intact</td></tr> <tr><td class="f">--propagate</td><td><code>add</code> only: descendants inherit the tag (mints <code>*</code> rather than <code>+</code>). Hazardous without <code>--raw</code> — an inherited symbolic name drifts to the newest descendant carrying it.</td></tr> <tr><td class="f">VALUE</td><td><code>add</code> only: the tag's value payload; omit it for a valueless tag (an empty VALUE has no spelling on the wire and is refused)</td></tr> </table> </section> <section class="cmd" id="amend"> <h3><span class="v">zit amend</span></h3> <p class="one">Attach display overrides to a check-in.</p> <pre class="usage">zit amend NAME [--comment TEXT] [--user NAME] [--date ISO] [--branch NAME]</pre> <div class="desc"><p>Mints one control artifact; history is never rewritten and the original check-in is untouched. At least one override flag is required.</p></div> <table class="flags"> <tr><td class="f">--comment TEXT</td><td>override the displayed comment</td></tr> <tr><td class="f">--user NAME</td><td>override the displayed author</td></tr> <tr><td class="f">--date ISO</td><td>override the displayed date (<code>YYYY-MM-DDTHH:MM:SS</code>)</td></tr> <tr><td class="f">--branch NAME</td><td>move the check-in (and its descendants) to branch NAME</td></tr> </table> </section> <section class="cmd" id="bisect"> <h3><span class="v">zit bisect</span></h3> <p class="one">Binary-search the check-in DAG for a good/bad boundary.</p> <pre class="usage">zit bisect bad NAME | good NAME | next | status | reset</pre> <div class="desc"><p>Mark a known-bad and known-good check-in; zit narrows the candidate span and moves the working tree to the midpoint for you to test, until one check-in remains. Moves go through the update engine — refused over a dirty tree, recoverable with <code>undo</code>. State lives in <code>.zit-checkout</code>, never in the repository.</p></div> <table class="flags"> <tr><td class="f">bad NAME</td><td>mark NAME as containing the defect</td></tr> <tr><td class="f">good NAME</td><td>mark NAME as free of the defect</td></tr> <tr><td class="f">next</td><td>move to (or re-announce) the current probe check-in</td></tr> <tr><td class="f">status</td><td>show the marks, the current probe, and the remaining count</td></tr> <tr><td class="f">reset</td><td>end the bisection and restore the starting check-in</td></tr> </table> </section> <!-- ===================== HISTORY & INSPECTION ===================== --> <h2 class="grp-head" data-grp="History & inspection">History & inspection</h2> <section class="cmd" id="log"> <h3><span class="v">zit log</span></h3> <p class="one">Show a check-in and its reachable parents.</p> <pre class="usage">zit log [--long] [-n N] [-R <path>] CHECKIN [PATH]</pre> <div class="desc"><p>Depth-first P-card order from CHECKIN, resolved like <code>info</code> — full or abbreviated ID, branch, tag, or reserved form. Each check-in shown once; a missing referenced parent shows once as a phantom. With PATH (exact repo-relative path, not a pattern), shows only check-ins where that file's effective content changes — including add and delete.</p> <p><b><code>--long</code></b> adds each check-in's own facts after its id, so a file-history page is one invocation rather than a second call per row:</p> <pre class="usage">$ zit log --long tip readme.md checkin 2b268f57… 2026-08-17T13:00:46.515 dmestas third checkin e35bda20… 2026-08-17T13:00:46.462 dmestas second checkin 347f4e8e… 2026-08-17T12:58:56.727 dmestas seed</pre> <p>The comment is last because it is the only field that may contain spaces; a check-in with no comment prints an empty trailing field, so the column count never varies. An absent timestamp or user reads <code>-</code>. Works with and without PATH.</p> <p><b>The default listing reads no artifact content.</b> It walks the derived parent adjacency — the same relation Fossil keeps in its <code>plink</code> table and traverses by indexed query — so it costs the same on a 300 MB repository as on a small one. <code>--long</code> is different in kind: the timestamp, user and comment live only in each check-in's manifest, so that form reconstructs and parses every one. On a repository whose manifests inline the whole file set the gap is large — seconds against milliseconds — which is worth knowing before scripting <code>--long</code> over deep history.</p> <p><span class="warn">Not an integrity check.</span> <code>log</code> trusts what the derive already validated and does not re-hash the artifacts it names, so it will happily list a check-in whose stored bytes have since been damaged. <a href="#verify"><code>verify</code></a> is the verb that re-reads and re-hashes the store; Fossil draws the same line between traversal and <code>test-integrity</code>.</p><p>The <code>--long</code> record grammar — <code>checkin <id> <timestamp> <user> <comment></code> — is a contract: consumers parse it positionally, and no display option, present or future, will change it.</p></div> <table class="flags"> <tr><td class="f">--long</td><td>add timestamp, user and comment to each entry</td></tr> <tr><td class="f">-n, --limit N</td><td>show only the N most recent entries; 0 means all</td></tr> <tr><td class="f">--weekday</td><td>show the UTC day of week beside each date — Ben Joffe’s branchless day-of-week computation (benjoffe.com/fast-day-of-week)</td></tr> <tr><td class="f">-R, --repo <path></td><td>read this repository instead; no working tree required</td></tr> </table> </section> <section class="cmd" id="timeline"> <h3><span class="v">zit timeline</span></h3> <p class="one">List every check-in, most recent first.</p> <pre class="usage">zit timeline [-n N] [--weekday]</pre> <div class="desc"><p>Per line: short hash, ISO timestamp, branch, first comment line. Merges are marked <code>M</code>. Order is event time descending with hash tiebreak — byte-identical across runs.</p></div> <table class="flags"> <tr><td class="f">-n, --limit N</td><td>show only the N most recent entries; 0 means all</td></tr> <tr><td class="f">-R, --repo <path></td><td>read this repository instead; no working tree required</td></tr> </table> </section> <section class="cmd" id="leaves"> <h3><span class="v">zit leaves</span></h3> <p class="one">List the open leaves of the check-in DAG.</p> <pre class="usage">zit leaves [--all] [--no-cache]</pre> <div class="desc"><p>Check-ins with no same-branch child and no active closed tag; full ID plus branch, most-recent first, current checkout marked <code>*</code>.</p></div> <table class="flags"> <tr><td class="f">--all</td><td>also list closed leaves, each marked <code>(closed)</code></td></tr> <tr><td class="f">--no-cache</td><td>bypass every derived sidecar and re-derive from the full log</td></tr> <tr><td class="f">-R, --repo <path></td><td>read this repository instead; no working tree required</td></tr> </table> </section> <section class="cmd" id="branch"> <h3><span class="v">zit branch</span></h3> <p class="one">List the repository's branches.</p> <pre class="usage">zit branch [--no-cache]</pre> <div class="desc"><p>One per line, by name, derived from active branch tags. Current checkout's branch marked <code>*</code>; a branch whose newest check-in is closed is marked <code>(closed)</code>. Accepts <code>--no-cache</code> and <code>-R</code>.</p></div> </section> <section class="cmd" id="info"> <h3><span class="v">zit info</span></h3> <p class="one">Resolve a name to a check-in and describe it.</p> <pre class="usage">zit info NAME [--no-cache]</pre> <div class="desc"><p>NAME: full ID, unique prefix, branch/tag, or reserved form (<code>tip</code>, <code>rid:</code>, <code>tag:</code>, <code>root:</code>, <code>start:</code>, <code>merge-in:</code>). Resolution follows recency, not open-leaf status. Accepts <code>--no-cache</code> and <code>-R</code>.</p></div> </section> <section class="cmd" id="ls"> <h3><span class="v">zit ls</span></h3> <p class="one">List the files of a check-in.</p> <pre class="usage">zit ls [VERSION] [--long|--size] [--under=PATH] [--no-cache]</pre> <div class="desc"><p><code><blob-id> <filename></code> per line in canonical order. VERSION resolves like <code>info</code>; omitted = current checkout (unborn is an error). Shows the effective set: delta check-ins are merged with their baseline, deletions omitted. Accepts <code>--no-cache</code> and <code>-R</code>.</p> <p><b><code>--long</code></b> adds three columns before the filename, which stays last so a name containing spaces is still unambiguous:</p> <pre class="usage">$ zit ls --long f04f3b9b… 11 - text readme.md 980e8347… 4 - binary src/blob.bin 59df8a6e… 18 x text tools/run.sh</pre> <p><code>perm</code> is the F card's own permission token — <code>x</code> executable, <code>l</code> symlink, <code>-</code> when the card carries none. It is the one fact no other verb can supply: it lives in the <i>manifest</i>, and the blob it names does not know it is executable, so without this a listing cannot mark executables and mis-renders a symlink (whose blob content <i>is</i> its target path). <code>l</code> reaches a repository only through <code>import-fossil</code> — <code>zit commit</code> refuses a symlink, matching fossil's <code>allow-symlinks</code> default.</p> <p><code>class</code> is <code>text</code>, <code>binary</code> (the content holds a NUL), or <code>absent</code> when the store cannot produce the bytes — a sparse elision, a shunned artifact, or a phantom. There <code>size</code> reads <code>0</code> because nothing was measured, not because the file is empty.</p> <p><b><code>--size</code></b> is the cheap half — <code><blob-id> <size> <perm> <filename></code>, no <code>class</code> column — and the split is exactly where the cost is. A size is read out of the frame header: a zlib frame states its uncompressed length outright, a delta carries its target size in its own header, so <code>--size</code> costs a header read per file and never resolves a delta chain. The binary verdict cannot be had that way, because it is "a NUL anywhere in the content" and the NUL may be the last byte. So a listing that needs names and sizes should ask for <code>--size</code>; one that needs the text/binary answer pays for <code>--long</code>. Under <code>--size</code> an unavailable blob prints <code>absent</code> in the size column, since there is no size to report.</p> <p><code>--long</code> reads every listed blob, so pair it with <code>--under</code> to pay only for what is displayed. The default two-field output reads no content and is unchanged.</p></div> <table class="flags"> <tr><td class="f">--long</td><td>add size, permission and text/binary columns</td></tr> <tr><td class="f">--size</td><td>add size and permission only; reads no blob content</td></tr> <tr><td class="f">--under=PATH</td><td>list only entries at or beneath PATH, matched on segment boundaries so <code>src</code> never matches <code>src-other</code>; PATH may name a single file</td></tr> </table> </section> <section class="cmd" id="cat"> <h3><span class="v">zit cat</span></h3> <p class="one">Emit a file's contents at a check-in.</p> <pre class="usage">zit cat FILE [VERSION] [--no-cache]</pre> <div class="desc"><p>Verbatim to stdout — no trailing newline added, binary passes through. Resolved against the effective file set (a delta check-in's own entry wins over the baseline; deletions read as not found). Emitted bytes are verified against the recorded hash. Accepts <code>--no-cache</code> and <code>-R</code>.</p></div> </section> <section class="cmd" id="artifact"> <h3><span class="v">zit artifact</span></h3> <p class="one">Print or describe a raw artifact by its hash.</p> <pre class="usage">zit artifact HASH [--raw]</pre> <div class="desc"><p>HASH is a full or ≥4-char hex prefix, resolved against every stored artifact including file blobs — branch/tag names not accepted (use <code>info</code>). Default is a summary: hash, size, type, per-card lines, Z self-checksum status. Accepts <code>-R</code>.</p></div> <table class="flags"> <tr><td class="f">--raw</td><td>write the exact stored bytes to stdout — hashing them reproduces HASH</td></tr> </table> </section> <section class="cmd" id="frames"> <h3><span class="v">zit frames</span></h3> <p class="one">Stream raw artifacts appended since a cursor (indexer feed).</p> <pre class="usage">zit frames [--since=CURSOR] [--verify=TOKEN] [--structural]</pre> <div class="desc"><p>The incremental read seam for an external consumer that only speaks through the zit binary. Each artifact appended after CURSOR is emitted as a header line <code>artifact HASH SIZE</code> followed by exactly SIZE raw bytes (whole artifacts — delta storage is resolved and every payload is verified against its name; byte-identical to <code>artifact HASH --raw</code>), then one trailer line <code>cursor CURSOR TOKEN</code>. Pass both trailer values back on the next poll; they are opaque. Without <code>--since</code>, the full census streams (the bootstrap read).</p> <p><code>--verify</code> proves the already-consumed prefix is still byte-identical: appends (commits, sync arrivals) keep a cursor valid; a store rewrite (<code>pack</code>, a shun reclaim) fails validation — the cursor is dead and the consumer re-reads from zero (derived indexes rebuild; loss is detectable by design, never silent). Shun-masked artifacts and phantoms are skipped with the cursor still advancing.</p> <p><code>--structural</code> emits only artifacts that parse as structural artifacts — the same gate the repository’s own crosslink applies — and skips content blobs, which dominate a full stream’s bytes. Skipped frames are neither hashed nor emitted; the cursor still advances over them, so the flag never changes what a later poll sees and filtered and unfiltered polls compose against one cursor. A poll whose new frames are all content emits nothing and exits 3, trailer included.</p> <p><b>Threads:</b> this is the one verb that uses more than one. Reconstruction stays single-threaded — it memoises delta bases, and consecutive artifacts routinely share one — while the per-artifact hash check runs on up to <code>min(cores, 8)</code> workers over a bounded batch. Emission order, the payload bytes, and the exit codes are identical at any worker count; the only thing that varies is how many cores a full-history read will occupy.</p></div> <table class="flags"> <tr><td class="f">--since=CURSOR</td><td>emit only artifacts appended after CURSOR</td></tr> <tr><td class="f">--verify=TOKEN</td><td>validate the consumed prefix before emitting</td></tr> <tr><td class="f">--structural</td><td>emit only structural artifacts; skip content blobs (cursor semantics unchanged)</td></tr> </table> <div class="desc"><p><b>Exit status</b> departs from the global convention: <code>0</code> artifacts emitted, <code>3</code> cursor current with nothing new (trailer still printed), <code>4</code> cursor invalid — re-read from <code>--since=0</code>. <code>1</code>/<code>2</code> keep their usual meanings. Accepts <code>-R</code> — a bare indexer is the primary consumer.</p></div> </section> <section class="cmd" id="crosslink"> <h3><span class="v">zit crosslink</span></h3> <p class="one">Stream derived per-check-in records (indexer feed).</p> <pre class="usage">zit crosslink [--no-cache]</pre> <div class="desc"><p>Every check-in’s <em>derived</em> record, in persisted order: identity, timestamp, author, comment, tag operations, and the files changed relative to the primary parent — the answers an indexer otherwise re-derives by parsing and expanding every manifest itself. The first line names the stream format (<code>crosslink 1</code>); each record is a <code>checkin</code> header, an optional <code>user</code> line, a length-framed <code>comment</code>, <code>tag</code> and <code>file (A|M|D)</code> lines, and <code>end</code>. Free-text tokens are card-escaped exactly as an artifact carries them, so every line is newline-safe. A rename reads as a delete plus an add.</p> <p>A record may say <code>files unknown</code> instead: a tree (sub-manifest) check-in, or a parent the store does not hold — the sqlite corpus carries exactly one, a 2009 check-in whose parent is genuinely absent. Unknown is deliberate; an empty change set is a claim, and the verb never claims what it did not compute.</p> <p>Records are served from the derived cache when it provably covers the whole log (the same key-and-shun-fingerprint discipline every read verb follows) and derived from the log when it does not; <code>--no-cache</code> forces the derivation, and the two outputs are byte-identical — pinned by test.</p></div> <table class="flags"> <tr><td class="f">--no-cache</td><td>derive from the log, bypassing the cache</td></tr> </table> </section> <section class="cmd" id="annotate"> <h3><span class="v">zit annotate</span></h3> <p class="one">Show which check-in introduced each line of a file.</p> <pre class="usage">zit annotate PATH [VERSION] [--full]</pre> <div class="desc"><p>Per line: 10-char ID prefix of the introducing check-in, its UTC ISO date, then the line. A line is attributed to the oldest check-in carrying it; a merged line is credited to its origin branch, never the merge itself. Only committed content is annotated; binary files are refused. VERSION resolves like <code>cat</code>'s (omitted = the current checkout), so with <code>-R</code> and a VERSION no working tree is required — a bare store annotates at any check-in.</p></div> <table class="flags"> <tr><td class="f">--full</td><td>emit the full artifact ID per line instead of the 10-char prefix — stable as an index key</td></tr> </table> </section> <section class="cmd" id="grep"> <h3><span class="v">zit grep</span></h3> <p class="one">Search history for a literal string, or find when it appeared and went.</p> <pre class="usage">zit grep [-n N] [--no-cache] [-R <path>] PATTERN PATH... zit grep -S|-G [-n N] [--no-cache] [-R <path>] PATTERN [PATH...]</pre> <div class="desc"><p>For each PATH the file's stored versions are walked newest-first, and every version whose content contains PATTERN is reported one line per matching line: <code><checkin12> <path>:<lineno>: <line></code>. A run of check-ins that did not touch PATH shares one blob and is reported once, not once per check-in.</p> <p><b>This searches history, not the working tree</b> — content that is not on disk, because it lives as deltas inside <code>.zit</code>. For the files in front of you, a dedicated tool such as ripgrep is faster than zit would be.</p> <p>PATTERN is a <span class="warn">literal string, not a regular expression</span>: zit carries no regex engine. <code>git log -S</code> is literal for the same reason and is the closer analogue; matching <code>fossil grep</code>'s POSIX expressions is later work.</p> <p>A version whose bytes the store cannot produce — elided by a sparse store, shunned, or a phantom — is counted and reported on stderr rather than skipped silently: a search that quietly examined less than it claimed looks exactly like one that found nothing. Binary versions (any NUL byte) are not searched.</p></div> <div class="desc"><p><b>The pickaxes</b> ask a different question: not "which versions contain this" but <i>"when did it appear, and when did it go"</i>. They report one line per check-in, newest first — <code><checkin12> <±N|~N> <path></code>. A string added once and deleted later is exactly two lines, where plain grep reports every version that held it in between. <code>PATH</code> becomes optional and narrows the walk instead of driving it.</p> <p><code>-S</code> fires when the <b>occurrence count</b> changed; <code>-G</code> when a <b>changed line</b> mentions the pattern. Renaming <code>x = SECRET</code> to <code>y = SECRET</code> leaves the count at one, so <code>-S</code> is silent and <code>-G</code> is not — git draws the same line between its two pickaxes, and it is why both exist. Give one or the other, never both.</p> <p>These are <code>git log -S</code>/<code>-G</code>. zit takes the string as the ordinary <code>PATTERN</code> operand rather than git's attached <code>-S<string></code>, so there is one way to say it. Both read only the blobs that actually changed — a path whose content hash matches its parent's cannot have moved either answer — so the walk is far cheaper than a diff per revision. Merges compare against the primary parent only, as git does; counting against every parent would report one appearance twice.</p></div> <table class="flags"> <tr><td class="f">-n, --limit N</td><td>stop after N matching versions; 0 means all</td></tr> <tr><td class="f">-S, --pickaxe</td><td>report check-ins where the occurrence <b>count</b> changed</td></tr> <tr><td class="f">-G, --diff-match</td><td>report check-ins where a <b>changed line</b> mentions PATTERN (runs the diff engine per changed path, so it costs more than <code>-S</code>)</td></tr> </table> <div class="desc"><p>Exit status departs from the convention: <b>0</b> at least one version matched, <b>1</b> nothing matched or the repository could not be read, <b>2</b> usage error.</p></div> </section> <section class="cmd" id="tarball"> <h3><span class="v">zit tarball</span></h3> <p class="one">Write a check-in's files as a gzip-compressed tar archive.</p> <pre class="usage">zit tarball VERSION [OUTPUTFILE] [--name DIR] [--include GLOBS] [--exclude GLOBS] [--list] [--no-cache]</pre> <div class="desc"><p>VERSION resolves like <code>info</code>. No working tree is involved and none is created — the file set is read straight from the repository, so <code>-R</code> works and a bare mirror can cut a release. Control files are never archived.</p> <p><b>Modes travel.</b> An F card's permission rides with the file: an executable stays executable and a symbolic link stays a link. A checkout-then-tar workaround routinely loses this, and the result looks perfectly correct — right files, right bytes — until someone unpacks it and <code>./configure</code> will not run.</p> <p><b>The bytes are reproducible.</b> Members appear in the check-in's own canonical order, carry the check-in's own timestamp rather than the clock, and record no uid, gid, user name, or build host. Two runs of one VERSION on different machines produce identical files, which is what makes the output usable in a release pipeline.</p> <p>Every member sits under one top-level directory. <code>--name</code> sets it; otherwise it is derived from OUTPUTFILE's basename with the archive suffix stripped (<code>release-1.2.tar.gz</code> gives <code>release-1.2</code>), falling back to the check-in's short id when there is no usable output name.</p> <p>An OUTPUTFILE of <code>/dev/null</code> or the empty string writes nothing; with <code>--list</code> that is how you preview an archive. A file the store does not hold — a sparse store elides out-of-cone content — is <b>refused by name</b> before any bytes are written, never omitted, which would look exactly like success.</p></div> <table class="flags"> <tr><td class="f">--name DIR</td><td>top-level directory inside the archive</td></tr> <tr><td class="f">--include GLOBS</td><td>archive only files matching this glob list</td></tr> <tr><td class="f">--exclude GLOBS</td><td>archive no file matching this glob list; a file matching <b>both</b> lists is excluded</td></tr> <tr><td class="f">--list</td><td>name the members instead of writing an archive</td></tr> <tr><td class="f">--no-cache</td><td>bypass the derived-state cache and re-derive</td></tr> </table> <p class="one">GLOBS is a comma- or newline-separated list in the same dialect as <code>ignore-glob</code>. Unlike fossil, a pattern may not be quoted, so a glob cannot contain a comma.</p> </section> <section class="cmd" id="zip"> <h3><span class="v">zit zip</span></h3> <p class="one">Write a check-in's files as a zip archive.</p> <pre class="usage">zit zip VERSION [OUTPUTFILE] [--name DIR] [--include GLOBS] [--exclude GLOBS] [--list] [--no-cache]</pre> <div class="desc"><p>Identical to <a href="#tarball"><code>tarball</code></a> in every respect but the container: same selection, same top-level directory rules, same reproducibility guarantee, and the same refusal to write a partial archive.</p> <p>Modes travel in the central directory's external attributes — where a reader looks, and where an executable silently becomes <code>0644</code> if a writer gets it wrong. A member is deflated only when that makes it smaller, so an already-compressed file is stored rather than grown.</p> <p>Members are limited to 4 GiB: zip64 is not emitted, and a file needing it is refused by name rather than written with a truncated size field. Use <code>tarball</code> for such a check-in.</p></div> <table class="flags"> <tr><td class="f">--name DIR</td><td>top-level directory inside the archive</td></tr> <tr><td class="f">--include GLOBS</td><td>archive only files matching this glob list</td></tr> <tr><td class="f">--exclude GLOBS</td><td>archive no file matching this glob list; exclude wins</td></tr> <tr><td class="f">--list</td><td>name the members instead of writing an archive</td></tr> <tr><td class="f">--no-cache</td><td>bypass the derived-state cache and re-derive</td></tr> </table> </section> <!-- ===================== SYNC ===================== --> <h2 class="grp-head" data-grp="Sync & serving">Sync & serving</h2> <section class="cmd" id="serve"> <h3><span class="v">zit serve</span></h3> <p class="one">Serve this repository over HTTP for sync.</p> <pre class="usage">zit serve [--port N]</pre> <div class="desc"><p>Answers POST <code>/xfer</code> with an <code>application/x-fossil</code> body per the sync protocol; one request per connection, single-threaded. Binds <code>127.0.0.1</code>. Stop with Ctrl-C. A running server picks up a concurrent shun or pack on its next request, no restart needed. A cone-scoped requester is served filtered; a <b>sparse</b> store refuses to be a clone origin for cones it does not cover (dense clone requests included) with one error card and no content — the requester's clone fails quoting the refusal.</p></div> <table class="flags"> <tr><td class="f">--port N</td><td>listen on port N (default 8080)</td></tr> </table> </section> <section class="cmd" id="pull"> <h3><span class="v">zit pull</span></h3> <p class="one">Fetch new artifacts from a peer.</p> <pre class="usage">zit pull [URL]</pre> <div class="desc"><p>Announces what this repo holds, requests everything it lacks, converges toward the peer's content. Anonymous. With URL omitted the saved remote is used; neither is an error.</p></div> </section> <section class="cmd" id="push"> <h3><span class="v">zit push</span></h3> <p class="one">Send this repository's artifacts to a peer.</p> <pre class="usage">zit push [URL] [--user U [--password P]]</pre> <div class="desc"><p>Uploads every artifact the peer lacks until both converge; nothing on this side changes. With URL omitted the saved remote is used. Without <code>--user</code> the push is anonymous; with it, a shared secret derived from the password signs the login card. After one successful authenticated push the derived secret (never the password) is saved at 0600, so later pushes need only <code>--user</code>.</p></div> <table class="flags"> <tr><td class="f">--user U</td><td>authenticate as login U (sync §6)</td></tr> <tr><td class="f">--password P</td><td>U's password; else <code>ZIT_PASSWORD</code>, else a no-echo prompt</td></tr> </table> </section> <section class="cmd" id="sync"> <h3><span class="v">zit sync</span></h3> <p class="one">Reconcile with a peer in both directions.</p> <pre class="usage">zit sync [URL] [--user U [--password P]]</pre> <div class="desc"><p>One session, not pull-then-push: uploads what the peer lacks and receives what this side lacks until convergence. History transfers both ways and artifacts are never discarded — there is no force-push. Authentication works exactly like <code>push</code>.</p></div> <table class="flags"> <tr><td class="f">--user U</td><td>authenticate as login U (sync §6)</td></tr> <tr><td class="f">--password P</td><td>U's password; else <code>ZIT_PASSWORD</code>, else a no-echo prompt</td></tr> </table> </section> <section class="cmd" id="remote"> <h3><span class="v">zit remote</span></h3> <p class="one">Show, set, or clear the default sync remote.</p> <pre class="usage">zit remote [URL | --unset]</pre> <div class="desc"><p>A single default remote, not a named registry. No argument prints it (including what <code>clone</code> saved); URL saves it verbatim. Never put a credential in the URL — authenticate with <code>--user</code> on push/sync instead.</p></div> <table class="flags"> <tr><td class="f">--unset</td><td>clear the saved remote</td></tr> </table> </section> <section class="cmd" id="settings"> <h3><span class="v">zit settings</span></h3> <p class="one">Show or change this repository's local settings.</p> <pre class="usage">zit settings [NAME [VALUE]]</pre> <div class="desc"><p>Bare lists every setting and its value; NAME prints one; NAME VALUE sets it. Every setting <i>this verb</i> edits is local to this repository and never crosses the wire — a clone comes up with defaults for all of them.</p> <p>That is a deliberate line, not an omission. <code>pgp-command</code> is a command line this machine <i>executes</i>, so a value a peer could set would be remote code execution; fossil classifies it <b>sensitive</b> and propagates it nowhere, and <code>clearsign</code> sits in no configuration group at all. Settings that legitimately travel do so by other mechanisms — tracked <code>.zit-settings/</code> files that clone like any other source, and the sync configuration groups.</p></div> <table class="flags"> <tr><td class="f">--global</td><td>read or write the operator-scope tier instead of this repository's</td></tr> <tr><td class="f">autosync on|off</td><td>pull from the saved remote before <code>commit</code>/<code>update</code>/<code>merge</code> and push after a successful commit (default on; failures warn and the local operation proceeds)</td></tr> <tr><td class="f">ignore-glob LIST</td><td>comma- or newline-separated globs that <code>extras</code>, <code>addremove</code>, <code>clean</code>, and <code>add</code>'s directory expansion leave alone (<code>*</code> matches <code>/</code>, so a directory is <code>zig-out/*</code>); empty clears it. Two <i>tracked</i> files override it and clone with the repository: <code>.zit-settings/ignore-glob</code> (this same fossil dialect) and a root <code>.zitignore</code> (gitignore dialect). Naming an ignored file explicitly still adds it.</td></tr> <tr><td class="f">clearsign on|off</td><td>clearsign every commit, as though <code>--sign</code> had been passed (default off) — fossil's setting of the same name</td></tr> <tr><td class="f">pgp-command CMD</td><td>the clearsign command, invoked as <code>CMD OUTFILE INFILE</code> (default <code>gpg --clearsign -o </code>, trailing space included; an <code>ssh</code>-family basename takes the detached form <code>CMD INFILE</code> and writes <code>INFILE.sig</code>; a false-ish value — <code>off</code>/<code>no</code>/<code>0</code>/<code>false</code> — disables signing). <code>$ZIT_PGP_COMMAND</code> overrides it, so one invocation can be redirected without editing the repository.</td></tr> </table> </section> <!-- ===================== STORE ===================== --> <h2 class="grp-head" data-grp="Store & maintenance">Store & maintenance</h2> <section class="cmd" id="stat"> <h3><span class="v">zit stat</span></h3> <p class="one">Report the store's size and artifact census.</p> <pre class="usage">zit stat [--artifacts] [--type=full|delta]</pre> <div class="desc"><p>One <code>field value</code> line per fact, read from the frames in <code>.zit</code> — nothing estimated: store/header/content/frame-overhead/config/shun bytes, artifact counts, and the full-form vs. delta partition of content bytes.</p></div> <table class="flags"> <tr><td class="f">--artifacts</td><td>list stored artifact hashes instead, one per line, ascending</td></tr> <tr><td class="f">--type=KIND</td><td>with <code>--artifacts</code>, only <code>full</code> or <code>delta</code> artifacts</td></tr> </table> </section> <section class="cmd" id="pack"> <h3><span class="v">zit pack</span></h3> <p class="one">Repack the store into a compact new generation.</p> <pre class="usage">zit pack [--drop-elided]</pre> <div class="desc"><p>Re-encodes full-form artifacts as deltas against their lineage and drops superseded config records; content and names are preserved exactly. Also where shunned content is <b>reclaimed</b> — <span class="warn">reclamation is not reversible</span>. Streams the whole store verifying every frame checksum and every reconstructed artifact hash, so it doubles as an integrity check. Written to a temp file and swapped in atomically; transiently needs room for old + new store.</p></div> <table class="flags"> <tr><td class="f">--drop-elided</td><td>on a sparse store, also drop artifacts that are out-of-cone and unreferenced by any spine — the sparse analog of shun reclamation, refetchable by widening (unlike a shun). Refused on a store whose worktree cone is wider than its store cone.</td></tr> </table> </section> <section class="cmd" id="verify"> <h3><span class="v">zit verify</span></h3> <p class="one">Check the store for damage (zit's fsck).</p> <pre class="usage">zit verify [-R <path>]</pre> <div class="desc"><p>Two passes: every frame's whole-frame checksum (catches on-disk bitflips), then every artifact reconstructed through its delta chain and re-hashed against its name (catches valid checksums over wrong bytes). Read-only, lockless, safe against a live <code>serve</code>. An incomplete final frame from a crash is reported but is not a fault. Unreclaimed shunned artifacts <i>are</i> verified and labelled. On a sparse store the summary counts the cone's members and the deliberately elided artifacts, and verification abstains from checksums it cannot reconstruct without out-of-cone bytes — elision is never reported as damage.</p></div> </section> <section class="cmd" id="config"> <h3><span class="v">zit config</span></h3> <p class="one">Fetch a configuration area (the shun table) from a peer.</p> <pre class="usage">zit config pull shun [URL]</pre> <div class="desc"><p>Fossil's <code>fossil configuration pull AREA</code>. Configuration otherwise travels at <b>clone time only</b>, and that is deliberate: a shun table that refreshed on every sync would let any peer suppress content in this repository on its own say-so. This verb is how you ask for it anyway — explicitly, naming the area and the peer.</p> <p>The <code>shun</code> area carries the peer's takedown records. <b>Rows are added, never removed</b>, so pulling cannot un-shun anything here. URL defaults to the saved remote; only configuration moves, no artifacts.</p></div> </section> <section class="cmd" id="trust"> <h3><span class="v">zit trust</span></h3> <p class="one">Accept or withdraw consent to run the project's checks.</p> <pre class="usage">zit trust [--yes] [--revoke]</pre> <div class="desc"><p>A project can ship its own pre-commit checks as a <i>tracked</i> file, <code>.zit-settings/before-commit</code>, so they clone with the code and are reviewable in a diff. That file is <b>inert on its own</b>: zit never runs it until this checkout says so. Cloning a repository therefore executes nothing, and neither does pulling an edit to a check you already accepted — <span class="warn">consent is pinned to the exact text you read</span>, so any change revokes it and asks again.</p> <p>Bare, it prints the command and its status and grants nothing: accepting something you have not been shown is not consent. Run it again with <code>--yes</code> to accept. A <code>before-commit</code> you set yourself with <code>zit settings</code> needs no trust record — writing the command is the consent.</p></div> <table class="flags"> <tr><td class="f">--yes</td><td>accept the declaration as shown; commits then run it</td></tr> <tr><td class="f">--revoke</td><td>withdraw consent; commits are refused until re-accepted</td></tr> </table> </section> <section class="cmd" id="purge"> <h3><span class="v">zit purge</span></h3> <p class="one">Remove leaf check-ins, recoverably, and reclaim their bytes.</p> <pre class="usage">zit purge checkins [--dry-run] [--explain] [--reason TEXT] [--force] CHECKIN... zit purge list zit purge undo ID zit purge obliterate [--force] ID...</pre> <div class="desc"><p>The tool for the ordinary accident — an <code>add .</code> that swept in a build tree, noticed before anyone else has the check-in. Removed artifacts go to a local <b>graveyard</b> that <code>purge undo</code> restores and <code>purge obliterate</code> discards for good.</p> <p><span class="warn">Not a takedown.</span> A purge is local, so a peer that already holds the content keeps it and a later sync fetches it straight back. Use <code>shun</code> when the content must never exist here again. This is the trap the two verbs exist to keep apart.</p> <p><b>A blob is removed only when no retained check-in still references it.</b> A file the purged check-ins share with surviving history stays exactly where it is — removing it would not free anything, it would turn held content into a phantom the next update fails on. Tags naming a purged check-in go with it.</p> <p><b>Only leaves can be purged</b>, judged over the whole named set: <code>zit purge checkins A B</code> where B is A's child is accepted, because afterwards nothing points at either. Naming A alone is refused rather than orphaning B. The check-in your working tree is on cannot be purged at all — the one refusal with no override.</p></div> <table class="flags"> <tr><td class="f">--dry-run</td><td>report the plan and exit 0 without changing anything</td></tr> <tr><td class="f">--explain</td><td>list every artifact the plan removes, not only the counts</td></tr> <tr><td class="f">--reason TEXT</td><td>record why, shown by <code>purge list</code></td></tr> <tr><td class="f">--force</td><td>actually remove (refuses by default; never prompts)</td></tr> </table> <div class="desc"><p>Reclaiming rewrites the store into a new generation and swaps it in atomically, exactly as <code>pack</code> does; a crash before the swap leaves the original untouched. The graveyard is local and disposable like the undo journal — never an artifact, never synced, not carried by a clone. Losing it costs recoverability, not data.</p> <p><b>No tombstone is recorded</b>, and that is precisely what makes a purge recoverable — and precisely why it is local. <code>shun</code> is the verb that also stops the content coming back.</p></div> </section> <section class="cmd" id="shun"> <h3><span class="v">zit shun</span></h3> <p class="one">Permanently remove artifacts and record them as never-fetch.</p> <pre class="usage">zit shun [--reason TEXT] [--defer] [--force] HASH... zit shun --list</pre> <div class="desc"><p><span class="warn">Irreversible</span> — <code>undo</code> does not recover it and the content cannot be re-fetched. By default it refuses and reports what would be destroyed; never prompts. Every sync direction honours the shun table (never requested, announced, served; arriving copies discarded). A hash not held here may still be shunned (given in full — a prefix can't be completed). The current checkout's check-in cannot be shunned. Removal rewrites the store atomically like <code>pack</code>; concurrent rewrites, or a store that changed mid-examination, refuse cleanly.</p></div> <table class="flags"> <tr><td class="f">--reason TEXT</td><td>record a single-line note (shown by <code>--list</code>)</td></tr> <tr><td class="f">--defer</td><td>record the shun; leave the bytes until <code>zit pack</code>. Reads answer identically, but the bytes stay reconstructible on disk until the rewrite. Still needs <code>--force</code>.</td></tr> <tr><td class="f">--force</td><td>actually record and remove</td></tr> <tr><td class="f">--list</td><td>list this repository's shun table and exit</td></tr> </table> <div class="desc"><p><b>The table travels to clones.</b> A clone of this repository adopts its shun table, and a clone from a Fossil server adopts that server's — over the same <code>/shun</code> configuration group Fossil uses, so the carriage is bidirectional with stock Fossil. This matters because shunning is a <i>takedown</i> mechanism: a clone that arrived without the table would, on its next sync with any third party, re-accept exactly the content an operator was ordered to remove.</p> <p>The carriage is one-way and clone-time only. An ordinary <code>pull</code> or <code>sync</code> never changes this table, so a peer can never shun an artifact here on its own say-so.</p></div> </section> <section class="cmd" id="checkpoint"> <h3><span class="v">zit checkpoint</span></h3> <p class="one">Show, build, verify, or drop the local rebuild checkpoint.</p> <pre class="usage">zit checkpoint [show | build [CHECKIN] | verify | drop]</pre> <div class="desc"><p>A local, disposable accelerator letting from-scratch derivation skip the log's permanently flat prefix. Sidecar only: never writes the repository, never synced, never history; deleting it changes no answer and <code>--no-cache</code> bypasses it. Planted automatically by the first full derivation and advanced by the ordinary open path.</p></div> <table class="flags"> <tr><td class="f">show</td><td>print the stored claim and whether it still validates (default)</td></tr> <tr><td class="f">build [CHECKIN]</td><td>build/rebuild, anchored at CHECKIN (full ID) or the newest check-in</td></tr> <tr><td class="f">verify</td><td>re-derive everything claimed from real history and compare byte-for-byte</td></tr> <tr><td class="f">drop</td><td>delete the checkpoint (missing = success)</td></tr> </table> </section> <section class="cmd" id="submanifest"> <h3><span class="v">zit submanifest</span></h3> <p class="one">Show or change this repository's sub-manifest settings.</p> <pre class="usage">zit submanifest [show | enable [--k N] | disable | retune --k N | analyze [--names PATH]]</pre> <div class="desc"><p>A sub-manifest lets a check-in carry its file set as a tree of shared, content-addressed slices instead of one flat F-card list. Emission is OFF by default; turning it on means peers below Fossil 2.29 are refused every S-bearing artifact, and it cannot retire artifacts already written. <span class="warn">The specification is provisional</span>; a repository containing one is a disposable test fixture. Settings are local and do not propagate to clones.</p></div> <table class="flags"> <tr><td class="f">show</td><td>print the settings and what commits currently emit (default)</td></tr> <tr><td class="f">enable [--k N]</td><td>turn emission on, pinning k the first time (chosen by replaying recent history; <code>--k</code> overrides)</td></tr> <tr><td class="f">disable</td><td>stop emitting new tree manifests (existing ones are permanent)</td></tr> <tr><td class="f">retune --k N</td><td>re-pin k — always manual. Next commit rewrites every leaf, once; no history changes</td></tr> <tr><td class="f">analyze [--names PATH]</td><td>measure the objective for k = 1..12, report the optimum, replay recent history through the real tree builder and delta encoder. Changes nothing. <code>--names</code> analyzes a sorted name list from PATH instead</td></tr> <tr><td class="f">--k N</td><td>the partition parameter, 1..12</td></tr> <tr><td class="f">--replay-all</td><td>analyze only: replay the WHOLE history instead of the recent window — the full-history evidence a conversion decision wants</td></tr> <tr><td class="f">-R <path></td><td>analyze only: measure a bare store with no checkout (the tip check-in's file set) — the pre-conversion gate for a cloned fossil history; settings actions refuse it</td></tr> </table> </section> <!-- ===================== GIT INTEROP ===================== --> <h2 class="grp-head" data-grp="Git & Fossil interop">Git & Fossil interop</h2> <section class="cmd" id="import-git"> <h3><span class="v">zit import-git</span></h3> <p class="one">Import a Git repository into a new zit repository.</p> <pre class="usage">zit import-git SOURCE DEST [--submanifest-k K | --no-submanifests] [--attribution committer|author] [--default-branch NAME] [--refuse-signed] [--provenance] [--max-export-payload-bytes BYTES] [--quiet]</pre> <div class="desc"><p>Transactional: DEST appears only after export, artifact creation, and ref publication all succeed. Branches are reconstructed from the commit graph, not the exporting refs: the main branch claims its whole first-parent chain to the root; other branches claim shallowest-tip first (name tiebreak) down first parents until reaching a claimed commit — so merges stay on the branch merged into, and history below a fork stays with the branch continuing through it.</p></div> <table class="flags"> <tr><td class="f">--submanifest-k K</td><td>pin K (1–12) instead of analyzing; use to match an existing repository's pin so two repos share sub-manifests</td></tr> <tr><td class="f">--no-submanifests</td><td>import with flat manifests and default settings; mutually exclusive with <code>--submanifest-k</code></td></tr> <tr><td class="f">--attribution committer|author</td><td>record committer (default) or author identities</td></tr> <tr><td class="f">--default-branch NAME</td><td>make NAME the main branch (default: <code>refs/heads/main</code> if present, else the first head). Also decides which branch keeps the history below every fork. NAME must exist in the source</td></tr> <tr><td class="f">--refuse-signed</td><td>refuse a source with signed commits/tags. Default: signatures are STRIPPED (Fossil has no signature card) and the import proceeds</td></tr> <tr><td class="f">--provenance</td><td>keep <code>.zit-import-map.jsonl</code> (per-object provenance) and <code>refs</code> in DEST; by default both are verified in staging then discarded. The repository is identical either way</td></tr> <tr><td class="f">--max-export-payload-bytes BYTES</td><td>cumulative Git payload bound (default 1 TiB); bounds total history, not memory — raise on <code>ExportPayloadLimitExceeded</code></td></tr> <tr><td class="f">--quiet</td><td>suppress progress diagnostics</td></tr> </table> </section> <section class="cmd" id="export-git"> <h3><span class="v">zit export-git</span></h3> <p class="one">Mirror a zit repository into a Git repository.</p> <pre class="usage">zit export-git SOURCE DEST [--email-domain DOMAIN] [--rebuild] [--quiet]</pre> <div class="desc"><p>Mirrors the canonical check-in graph through <code>git fast-import</code> — flat, sub-manifest, and delta-manifest history alike. Incremental and resumable: a local-only mark map in <code>DEST/.zit-export-state</code> makes re-runs export only new check-ins; deleting it costs a full re-export, never a wrong answer. A source holding S-card artifacts is a provisional-format fixture, and the exported mirror is disposable (noted once per run).</p></div> <table class="flags"> <tr><td class="f">--email-domain DOMAIN</td><td>synthesized identity domain (default <code>zit.invalid</code>)</td></tr> <tr><td class="f">--rebuild</td><td>ignore prior state; DEST must carry no refs</td></tr> <tr><td class="f">--quiet</td><td>suppress the provisional-fixture notice</td></tr> </table> </section> <section class="cmd" id="export-fossil"> <h3><span class="v">exporting to fossil</span></h3> <p class="one">There is no <code>export-fossil</code> verb — and the asymmetry with <code>import-fossil</code> is deliberate.</p> <pre class="usage">zit serve --port 8080 & fossil clone http://127.0.0.1:8080/ out.fossil</pre> <div class="desc"><p>zit speaks fossil's sync protocol, so a stock <code>fossil</code> clones straight from <code>zit serve</code> — no converter, no verb. That is the export path, and it is verified on every CI run: <code>scripts/roundtrip-fossil.sh</code> takes a repository <i>fossil → zit → fossil</i> and compares the artifact sets, fossil's own <code>test-integrity</code> verdict, the event counts, and the shun table.</p> <p><code>export-git</code> exists because git speaks a different protocol entirely and needs <code>fast-import</code>. Fossil needs nothing.</p> <p>What this route does <i>not</i> give you is a single <code>.fossil</code> file from one command — it needs a free port, a background process, a readiness wait, and a second binary. That gap is tracked separately.</p></div> </section> <section class="cmd" id="import-fossil"> <h3><span class="v">zit import-fossil</span></h3> <p class="one">Bring a fossil repository's history into zit — the compatibility consequence is chosen at import time.</p> <pre class="usage">zit import-fossil SOURCE DIR (--compatible | --submanifest) [--k N] [--force]</pre> <div class="desc"><p><b>The mode is the decision and it is permanent, so there is no default.</b> SOURCE is a sync URL (for <code>--compatible</code>) or a local store already cloned from the fossil origin (for <code>--submanifest</code>); DIR is created.</p> <p><code>--compatible</code> preserves the artifact corpus <b>byte-exact</b>: same hashes, same project code, flat manifests. The result is a first-class member of the origin's sync world — it pulls, pushes, and syncs with the fossil repository it came from. Fossil's sync protocol is the import path; no SQLite is ever read. This is the prerequisite for the eventual fossil → zit → fossil round trip (#73): a later export reproduces the exact fossil, server settings aside.</p> <p><code>--submanifest</code> is a <b>rebirth</b>: every check-in re-mints as a sub-manifest tree under a NEW project code — uniform tree history from the first commit, at the price of a permanent break (never syncs with the origin; PGP signatures cannot survive rehashing and are stripped and counted; clusters drop). Before converting, the WHOLE history replays through the real tree builder and a conversion the evidence prices worse than flat is <b>refused</b> without <code>--force</code>. Every (old, new) check-in pair is proven file-set-equivalent before anything is published, counts are reconciled against a full corpus census, publication is staged with an atomic rename as the only commit point (a failed run leaves a sweepable <code>.zit-tmp-*</code>, never a half-repo), and the old→new provenance map ships as a deterministic technote inside the result, so external hash citations stay resolvable by lookup.</p></div> <table class="flags"> <tr><td class="f">--compatible</td><td>verbatim corpus; stays in the origin's sync world</td></tr> <tr><td class="f">--submanifest</td><td>re-mint as sub-manifest trees; new project code, gated by whole-history replay</td></tr> <tr><td class="f">--k N</td><td>submanifest only: override the gate's partition parameter (1..12)</td></tr> <tr><td class="f">--force</td><td>submanifest only: convert even when the replay gate refuses</td></tr> </table> </section> <!-- ===================== MISC ===================== --> <h2 class="grp-head" data-grp="Miscellaneous">Miscellaneous</h2> <section class="cmd" id="version"> <h3><span class="v">zit version</span></h3> <p class="one">Print the version.</p> <pre class="usage">zit version</pre> </section> <section class="cmd" id="help"> <h3><span class="v">zit help</span></h3> <p class="one">Show help — global, or for one command.</p> <pre class="usage">zit help [COMMAND | TOPIC]</pre> </section> <!-- ===================== MACHINE VERBS ===================== --> <h2 class="grp-head" data-grp="Machine verbs (plumbing)">Machine verbs (plumbing)</h2> <p class="desc" style="max-width:72ch; color:var(--muted); font-size:0.9rem;">Verbs for other <i>programs</i> (quarry's write plane) — wiki, ticket, forum, technote — deliberately absent from the human <code>zit help</code> command list — unlisted, never undocumented: each answers its own <code>--help</code> and <code>zit help <verb></code>. Machine contract: stdout carries exactly the result line, diagnostics go to stderr, exit codes are exact (0 recorded, 1 user error, 2 usage). All operate on a bare store via <code>-R</code> and serialize on the store's write lock, so concurrent callers queue instead of tearing the log.</p> <section class="cmd" id="wiki"> <h3><span class="v">zit wiki</span></h3> <p class="one">Record a wiki artifact (machine verb).</p> <pre class="usage">zit wiki commit <title> [-R <repo>] [--user NAME] [--mimetype TYPE] < body</pre> <div class="desc"><p>Records a wiki artifact for <i>title</i> with the body read from stdin to EOF. Prints the new artifact hash — nothing else — on stdout. Cards per the artifact-format RFC: D (writer clock), L (title), N (only with <code>--mimetype</code>; readers assume Fossil wiki markup without it), U, W, Z. An empty title or author is refused.</p></div> <table class="flags"> <tr><td class="f">--user NAME</td><td>author to record (default: <code>$USER</code>); empty refused</td></tr> <tr><td class="f">--mimetype TYPE</td><td>record an N card, e.g. <code>text/x-markdown</code></td></tr> </table> </section> <section class="cmd" id="ticket"> <h3><span class="v">zit ticket</span></h3> <p class="one">Record a ticket-change artifact (machine verb).</p> <pre class="usage">zit ticket new [-R <repo>] [--user NAME] field=value ... zit ticket set <ticket-id> [-R <repo>] [--user NAME] field=value ...</pre> <div class="desc"><p>Records a ticket-change artifact: one J card per <code>field=value</code>, sorted ascending (a repeated field is refused, not resolved by position), plus K, D, U, Z. <code>new</code> mints a fresh 40-hex ticket id and prints <code><ticket-id> <artifact-hash></code>; <code>set</code> requires an id some stored ticket artifact already carries — a typo'd id exits 1 instead of silently opening a second ticket — and prints the artifact hash.</p></div> <table class="flags"> <tr><td class="f">--user NAME</td><td>author to record (default: <code>$USER</code>); empty refused</td></tr> </table> </section> <section class="cmd" id="forum"> <h3><span class="v">zit forum</span></h3> <p class="one">Record forum-thread artifacts (machine verb).</p> <pre class="usage">zit forum post [-R <repo>] [--user NAME] [--title TITLE] [--mimetype TYPE] < body zit forum reply <post-hash> [-R <repo>] [--user NAME] [--mimetype TYPE] < body</pre> <div class="desc"><p><code>post</code> opens a new thread — <code>--title</code> is the thread's H card, the stdin body its W card. <code>reply</code> answers <i>post-hash</i> inside its thread, recording G (the thread root, resolved transitively — replying to a reply still names the root) and I (in-reply-to). An unknown post-hash exits 1; <code>--title</code> on a reply is refused (a forum artifact carries H or I, never both). Prints the new artifact hash on stdout.</p> <p>Each post declares its own <code>--mimetype</code> (the N card); nothing propagates the thread root's to its replies, and omitting it leaves the card absent so the reader applies its own default.</p></div> <table class="flags"> <tr><td class="f">--user NAME</td><td>author to record (default: <code>$USER</code>); empty refused</td></tr> <tr><td class="f">--title TITLE</td><td>the thread title (post only; a reply inherits it)</td></tr> <tr><td class="f">--mimetype TYPE</td><td>the body's mimetype (N card), e.g. <code>text/x-markdown</code>; same spelling as <code>wiki commit</code></td></tr> </table> </section> <section class="cmd" id="technote"> <h3><span class="v">zit technote</span></h3> <p class="one">Record a technote (event) artifact (machine verb).</p> <pre class="usage">zit technote add [-R <repo>] [--user NAME] [--comment TEXT] [--tag NAME]... [--mimetype TYPE] [--event EVENT-ID] [--time TIMESTAMP] < body</pre> <div class="desc"><p>Body from stdin. <code>--event</code> names an existing technote to record a new edit of (latest wins at read time); omitted, a fresh 40-hex event id is minted. <code>--time</code> sets the E card's own event timestamp (any form the artifact format accepts; default now); the D card is always the writer clock. Prints <code><event-id> <artifact-hash></code> on stdout.</p> <p><b><code>--comment</code> is the note's title</b> (the C card), and it is what a timeline renders as the note's name. <span class="warn">No later verb can attach it</span> — a note recorded without one shows its event id forever — so give it at the mint or not at all. <code>--tag</code> adds a symbolic tag the note is found by, written <code>T +sym-NAME *</code> exactly as fossil writes it; repeatable, sorted into the ascending run the format requires. Names carrying whitespace, the same name twice, and an empty comment are all refused.</p></div> <table class="flags"> <tr><td class="f">--user NAME</td><td>author to record (default: <code>$USER</code>); empty refused</td></tr> <tr><td class="f">--comment TEXT</td><td>the note's title (C card); unattachable afterwards, empty refused</td></tr> <tr><td class="f">--tag NAME</td><td>a symbolic tag the note is found by (<code>T +sym-NAME *</code>); repeatable, at most 64</td></tr> <tr><td class="f">--mimetype TYPE</td><td>the body's mimetype (N card)</td></tr> <tr><td class="f">--event EVENT-ID</td><td>the technote to add an edit to</td></tr> <tr><td class="f">--time TIMESTAMP</td><td>the event's own timestamp (E card)</td></tr> </table> </section> <p id="nores">No commands match that filter.</p> </main> </div> <script> (function () { // The cards belonging to one group heading: every `.cmd` sibling up to the // next heading. Non-card siblings are SKIPPED, not treated as the end — a // group may open with an intro paragraph (the machine verbs do), and a walk // that stopped at the first non-card left that group with no sidebar links // and hid its heading on every filter keystroke. function cardsAfter(h) { var out = []; var el = h.nextElementSibling; while (el && !(el.tagName === 'H2' && el.classList.contains('grp-head'))) { if (el.classList.contains('cmd')) out.push(el); el = el.nextElementSibling; } return out; } // Build the sidebar from the group headings and command cards. var toc = document.getElementById('toc'); var frag = document.createDocumentFragment(); document.querySelectorAll('h2.grp-head').forEach(function (h) { var g = document.createElement('div'); g.className = 'grp'; var t = document.createElement('div'); t.className = 'grp-title'; t.textContent = h.textContent; g.appendChild(t); cardsAfter(h).forEach(function (el) { var a = document.createElement('a'); a.href = '#' + el.id; a.textContent = el.id; g.appendChild(a); }); frag.appendChild(g); }); var box = document.createElement('details'); var sum = document.createElement('summary'); sum.textContent = 'Jump to a command'; box.appendChild(sum); box.appendChild(frag); toc.appendChild(box); // Open on the sticky-rail layout, collapsed on the narrow one — where the // index otherwise pushes the page title a screenful down. Re-evaluated on // resize so rotating a tablet does not strand it in the wrong state. var wide = window.matchMedia('(min-width: 801px)'); function syncIndex() { box.open = wide.matches; } syncIndex(); if (wide.addEventListener) wide.addEventListener('change', syncIndex); // Live filter: match against the card's full text. var input = document.getElementById('filter'); var cards = Array.prototype.slice.call(document.querySelectorAll('.cmd')); var heads = Array.prototype.slice.call(document.querySelectorAll('h2.grp-head')); var nores = document.getElementById('nores'); input.addEventListener('input', function () { var q = input.value.trim().toLowerCase(); var any = false; cards.forEach(function (c) { var hit = !q || c.textContent.toLowerCase().indexOf(q) !== -1; c.classList.toggle('hidden', !hit); if (hit) any = true; }); heads.forEach(function (h) { var group = cardsAfter(h); var vis = group.some(function (el) { return !el.classList.contains('hidden'); }); h.classList.toggle('hidden', !vis); // A group's intro paragraph follows its heading's visibility, so a // filtered-out group does not leave prose stranded under nothing. var el = h.nextElementSibling; while (el && !(el.tagName === 'H2' && el.classList.contains('grp-head'))) { if (!el.classList.contains('cmd')) el.classList.toggle('hidden', !vis); el = el.nextElementSibling; } }); nores.style.display = any ? 'none' : 'block'; }); })(); </script> </body> </html>